1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Linguist of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "lupdate.h"
43 
44 #include <translator.h>
45 
46 #include <QtCore/QBitArray>
47 #include <QtCore/QDebug>
48 #include <QtCore/QFileInfo>
49 #include <QtCore/QStack>
50 #include <QtCore/QString>
51 #include <QtCore/QTextCodec>
52 #include <QtCore/QTextStream>
53 #include <QtCore/QCoreApplication>
54 
55 #include <iostream>
56 
57 #include <ctype.h>              // for isXXX()
58 
59 QT_BEGIN_NAMESPACE
60 
61 class LU {
62     Q_DECLARE_TR_FUNCTIONS(LUpdate)
63 };
64 
65 /* qmake ignore Q_OBJECT */
66 
67 static QString MagicComment(QLatin1String("TRANSLATOR"));
68 
69 #define STRING(s) static QString str##s(QLatin1String(#s))
70 
71 //#define DIAGNOSE_RETRANSLATABILITY // FIXME: should make a runtime option of this
72 
73 class HashString {
74 public:
HashString()75     HashString() : m_hash(0x80000000) {}
HashString(const QString & str)76     explicit HashString(const QString &str) : m_str(str), m_hash(0x80000000) {}
setValue(const QString & str)77     void setValue(const QString &str) { m_str = str; m_hash = 0x80000000; }
value() const78     const QString &value() const { return m_str; }
operator ==(const HashString & other) const79     bool operator==(const HashString &other) const { return m_str == other.m_str; }
80 private:
81     QString m_str;
82     // qHash() of a QString is only 28 bits wide, so we can use
83     // the highest bit(s) as the "hash valid" flag.
84     mutable uint m_hash;
85     friend uint qHash(const HashString &str);
86 };
87 
qHash(const HashString & str)88 uint qHash(const HashString &str)
89 {
90     if (str.m_hash & 0x80000000)
91         str.m_hash = qHash(str.m_str);
92     return str.m_hash;
93 }
94 
95 class HashStringList {
96 public:
HashStringList(const QList<HashString> & list)97     explicit HashStringList(const QList<HashString> &list) : m_list(list), m_hash(0x80000000) {}
value() const98     const QList<HashString> &value() const { return m_list; }
operator ==(const HashStringList & other) const99     bool operator==(const HashStringList &other) const { return m_list == other.m_list; }
100 private:
101     QList<HashString> m_list;
102     mutable uint m_hash;
103     friend uint qHash(const HashStringList &list);
104 };
105 
qHash(const HashStringList & list)106 uint qHash(const HashStringList &list)
107 {
108     if (list.m_hash & 0x80000000) {
109         uint hash = 0;
110         foreach (const HashString &qs, list.m_list) {
111             hash ^= qHash(qs) ^ 0x0ad9f526;
112             hash = ((hash << 13) & 0x0fffffff) | (hash >> 15);
113         }
114         list.m_hash = hash;
115     }
116     return list.m_hash;
117 }
118 
119 typedef QList<HashString> NamespaceList;
120 
121 struct Namespace {
122 
NamespaceNamespace123     Namespace() :
124             classDef(this),
125             hasTrFunctions(false), complained(false)
126     {}
~NamespaceNamespace127     ~Namespace()
128     {
129         qDeleteAll(children);
130     }
131 
132     QHash<HashString, Namespace *> children;
133     QHash<HashString, NamespaceList> aliases;
134     QList<HashStringList> usings;
135 
136     // Class declarations set no flags and create no namespaces, so they are ignored.
137     // Class definitions may appear multiple times - but only because we are trying to
138     // "compile" all sources irrespective of build configuration.
139     // Nested classes may be forward-declared inside a definition, and defined in another file.
140     // The latter will detach the class' child list, so clones need a backlink to the original
141     // definition (either one in case of multiple definitions).
142     // Namespaces can have tr() functions as well, so we need to track parent definitions for
143     // them as well. The complication is that we may have to deal with a forrest instead of
144     // a tree - in that case the parent will be arbitrary. However, it seem likely that
145     // Q_DECLARE_TR_FUNCTIONS would be used either in "class-like" namespaces with a central
146     // header or only locally in a file.
147     Namespace *classDef;
148 
149     QString trQualification;
150 
151     bool hasTrFunctions;
152     bool complained; // ... that tr functions are missing.
153 };
154 
155 static int nextFileId;
156 
157 class VisitRecorder {
158 public:
VisitRecorder()159     VisitRecorder()
160     {
161         m_ba.resize(nextFileId);
162     }
tryVisit(int fileId)163     bool tryVisit(int fileId)
164     {
165         if (m_ba.at(fileId))
166             return false;
167         m_ba[fileId] = true;
168         return true;
169     }
170 private:
171     QBitArray m_ba;
172 };
173 
174 struct ParseResults {
175     int fileId;
176     Namespace rootNamespace;
177     QSet<const ParseResults *> includes;
178 };
179 
180 struct IncludeCycle {
181     QSet<QString> fileNames;
182     QSet<const ParseResults *> results;
183 };
184 
185 typedef QHash<QString, IncludeCycle *> IncludeCycleHash;
186 typedef QHash<QString, const Translator *> TranslatorHash;
187 
188 class CppFiles {
189 
190 public:
191     static QSet<const ParseResults *> getResults(const QString &cleanFile);
192     static void setResults(const QString &cleanFile, const ParseResults *results);
193     static const Translator *getTranslator(const QString &cleanFile);
194     static void setTranslator(const QString &cleanFile, const Translator *results);
195     static bool isBlacklisted(const QString &cleanFile);
196     static void setBlacklisted(const QString &cleanFile);
197     static void addIncludeCycle(const QSet<QString> &fileNames);
198 
199 private:
200     static IncludeCycleHash &includeCycles();
201     static TranslatorHash &translatedFiles();
202     static QSet<QString> &blacklistedFiles();
203 };
204 
205 class CppParser {
206 
207 public:
208     CppParser(ParseResults *results = 0);
209     void setInput(const QString &in);
210     void setInput(QTextStream &ts, const QString &fileName);
setTranslator(Translator * _tor)211     void setTranslator(Translator *_tor) { tor = _tor; }
212     void parse(const QString &initialContext, ConversionData &cd, const QStringList &includeStack, QSet<QString> &inclusions);
213     void parseInternal(ConversionData &cd, const QStringList &includeStack, QSet<QString> &inclusions);
214     const ParseResults *recordResults(bool isHeader);
deleteResults()215     void deleteResults() { delete results; }
216 
217     struct SavedState {
218         NamespaceList namespaces;
219         QStack<int> namespaceDepths;
220         NamespaceList functionContext;
221         QString functionContextUnresolved;
222         QString pendingContext;
223     };
224 
225 private:
226     struct IfdefState {
IfdefStateCppParser::IfdefState227         IfdefState() {}
IfdefStateCppParser::IfdefState228         IfdefState(int _bracketDepth, int _braceDepth, int _parenDepth) :
229             bracketDepth(_bracketDepth),
230             braceDepth(_braceDepth),
231             parenDepth(_parenDepth),
232             elseLine(-1)
233         {}
234 
235         SavedState state;
236         int bracketDepth, bracketDepth1st;
237         int braceDepth, braceDepth1st;
238         int parenDepth, parenDepth1st;
239         int elseLine;
240     };
241 
242     std::ostream &yyMsg(int line = 0);
243 
244     uint getChar();
245     uint getToken();
246     bool getMacroArgs();
247     bool match(uint t);
248     bool matchString(QString *s);
249     bool matchEncoding(bool *utf8);
250     bool matchStringOrNull(QString *s);
251     bool matchExpression();
252 
253     QString transcode(const QString &str, bool utf8);
254     void recordMessage(
255         int line, const QString &context, const QString &text, const QString &comment,
256         const QString &extracomment, const QString &msgid, const TranslatorMessage::ExtraData &extra,
257         bool utf8, bool plural);
258 
259     void processInclude(const QString &file, ConversionData &cd,
260                         const QStringList &includeStack, QSet<QString> &inclusions);
261 
262     void saveState(SavedState *state);
263     void loadState(const SavedState *state);
264 
265     static QString stringifyNamespace(const NamespaceList &namespaces);
266     static QStringList stringListifyNamespace(const NamespaceList &namespaces);
267     typedef bool (CppParser::*VisitNamespaceCallback)(const Namespace *ns, void *context) const;
268     bool visitNamespace(const NamespaceList &namespaces, int nsCount,
269                         VisitNamespaceCallback callback, void *context,
270                         VisitRecorder &vr, const ParseResults *rslt) const;
271     bool visitNamespace(const NamespaceList &namespaces, int nsCount,
272                         VisitNamespaceCallback callback, void *context) const;
273     static QStringList stringListifySegments(const QList<HashString> &namespaces);
274     bool qualifyOneCallbackOwn(const Namespace *ns, void *context) const;
275     bool qualifyOneCallbackUsing(const Namespace *ns, void *context) const;
276     bool qualifyOne(const NamespaceList &namespaces, int nsCnt, const HashString &segment,
277                     NamespaceList *resolved, QSet<HashStringList> *visitedUsings) const;
278     bool qualifyOne(const NamespaceList &namespaces, int nsCnt, const HashString &segment,
279                     NamespaceList *resolved) const;
280     bool fullyQualify(const NamespaceList &namespaces, int nsCnt,
281                       const QList<HashString> &segments, bool isDeclaration,
282                       NamespaceList *resolved, QStringList *unresolved) const;
283     bool fullyQualify(const NamespaceList &namespaces,
284                       const QList<HashString> &segments, bool isDeclaration,
285                       NamespaceList *resolved, QStringList *unresolved) const;
286     bool fullyQualify(const NamespaceList &namespaces,
287                       const QString &segments, bool isDeclaration,
288                       NamespaceList *resolved, QStringList *unresolved) const;
289     bool findNamespaceCallback(const Namespace *ns, void *context) const;
290     const Namespace *findNamespace(const NamespaceList &namespaces, int nsCount = -1) const;
291     void enterNamespace(NamespaceList *namespaces, const HashString &name);
292     void truncateNamespaces(NamespaceList *namespaces, int length);
293     Namespace *modifyNamespace(NamespaceList *namespaces, bool haveLast = true);
294 
295     enum {
296         Tok_Eof, Tok_class, Tok_friend, Tok_namespace, Tok_using, Tok_return,
297         Tok_tr, Tok_trUtf8, Tok_translate, Tok_translateUtf8, Tok_trid,
298         Tok_Q_OBJECT, Tok_Q_DECLARE_TR_FUNCTIONS,
299         Tok_Ident, Tok_Comment, Tok_String, Tok_Arrow, Tok_Colon, Tok_ColonColon,
300         Tok_Equals, Tok_LeftBracket, Tok_RightBracket,
301         Tok_LeftBrace, Tok_RightBrace, Tok_LeftParen, Tok_RightParen, Tok_Comma, Tok_Semicolon,
302         Tok_Null, Tok_Integer,
303         Tok_QuotedInclude, Tok_AngledInclude,
304         Tok_Other
305     };
306 
307     // Tokenizer state
308     QString yyFileName;
309     int yyCh;
310     bool yyAtNewline;
311     bool yyCodecIsUtf8;
312     bool yyForceUtf8;
313     QString yyWord;
314     qlonglong yyInteger;
315     QStack<IfdefState> yyIfdefStack;
316     int yyBracketDepth;
317     int yyBraceDepth;
318     int yyParenDepth;
319     int yyLineNo;
320     int yyCurLineNo;
321     int yyBracketLineNo;
322     int yyBraceLineNo;
323     int yyParenLineNo;
324 
325     // the string to read from and current position in the string
326     QTextCodec *yySourceCodec;
327     QString yyInStr;
328     const ushort *yyInPtr;
329 
330     // Parser state
331     uint yyTok;
332 
333     NamespaceList namespaces;
334     QStack<int> namespaceDepths;
335     NamespaceList functionContext;
336     QString functionContextUnresolved;
337     QString prospectiveContext;
338     QString pendingContext;
339     ParseResults *results;
340     Translator *tor;
341     bool directInclude;
342 
343     SavedState savedState;
344     int yyMinBraceDepth;
345     bool inDefine;
346 };
347 
CppParser(ParseResults * _results)348 CppParser::CppParser(ParseResults *_results)
349 {
350     tor = 0;
351     if (_results) {
352         results = _results;
353         directInclude = true;
354     } else {
355         results = new ParseResults;
356         directInclude = false;
357     }
358     yyBracketDepth = 0;
359     yyBraceDepth = 0;
360     yyParenDepth = 0;
361     yyCurLineNo = 1;
362     yyBracketLineNo = 1;
363     yyBraceLineNo = 1;
364     yyParenLineNo = 1;
365     yyAtNewline = true;
366     yyMinBraceDepth = 0;
367     inDefine = false;
368 }
369 
370 
yyMsg(int line)371 std::ostream &CppParser::yyMsg(int line)
372 {
373     return std::cerr << qPrintable(yyFileName) << ':' << (line ? line : yyLineNo) << ": ";
374 }
375 
setInput(const QString & in)376 void CppParser::setInput(const QString &in)
377 {
378     yyInStr = in;
379     yyFileName = QString();
380     yySourceCodec = 0;
381     yyForceUtf8 = true;
382 }
383 
setInput(QTextStream & ts,const QString & fileName)384 void CppParser::setInput(QTextStream &ts, const QString &fileName)
385 {
386     yyInStr = ts.readAll();
387     yyFileName = fileName;
388     yySourceCodec = ts.codec();
389     yyForceUtf8 = false;
390 }
391 
392 /*
393   The first part of this source file is the C++ tokenizer.  We skip
394   most of C++; the only tokens that interest us are defined here.
395   Thus, the code fragment
396 
397       int main()
398       {
399           printf("Hello, world!\n");
400           return 0;
401       }
402 
403   is broken down into the following tokens (Tok_ omitted):
404 
405       Ident Ident LeftParen RightParen
406       LeftBrace
407           Ident LeftParen String RightParen Semicolon
408           return Semicolon
409       RightBrace.
410 
411   The 0 doesn't produce any token.
412 */
413 
getChar()414 uint CppParser::getChar()
415 {
416     const ushort *uc = yyInPtr;
417     forever {
418         ushort c = *uc;
419         if (!c) {
420             yyInPtr = uc;
421             return EOF;
422         }
423         ++uc;
424         if (c == '\\') {
425             ushort cc = *uc;
426             if (cc == '\n') {
427                 ++yyCurLineNo;
428                 ++uc;
429                 continue;
430             }
431             if (cc == '\r') {
432                 ++yyCurLineNo;
433                 ++uc;
434                 if (*uc == '\n')
435                     ++uc;
436                 continue;
437             }
438         }
439         if (c == '\r') {
440             if (*uc == '\n')
441                 ++uc;
442             c = '\n';
443             ++yyCurLineNo;
444             yyAtNewline = true;
445         } else if (c == '\n') {
446             ++yyCurLineNo;
447             yyAtNewline = true;
448         } else if (c != ' ' && c != '\t' && c != '#') {
449             yyAtNewline = false;
450         }
451         yyInPtr = uc;
452         return c;
453     }
454 }
455 
456 // This ignores commas, parens and comments.
457 // IOW, it understands only a single, simple argument.
getMacroArgs()458 bool CppParser::getMacroArgs()
459 {
460     // Failing this assertion would mean losing the preallocated buffer.
461     Q_ASSERT(yyWord.isDetached());
462     yyWord.resize(0);
463 
464     while (isspace(yyCh))
465         yyCh = getChar();
466     if (yyCh != '(')
467         return false;
468     do {
469         yyCh = getChar();
470     } while (isspace(yyCh));
471     ushort *ptr = (ushort *)yyWord.unicode();
472     while (yyCh != ')') {
473         if (yyCh == EOF)
474             return false;
475         *ptr++ = yyCh;
476         yyCh = getChar();
477     }
478     yyCh = getChar();
479     for (; ptr != (ushort *)yyWord.unicode() && isspace(*(ptr - 1)); --ptr) ;
480     yyWord.resize(ptr - (ushort *)yyWord.unicode());
481     return true;
482 }
483 
484 STRING(Q_OBJECT);
485 STRING(Q_DECLARE_TR_FUNCTIONS);
486 STRING(QT_TR_NOOP);
487 STRING(QT_TRID_NOOP);
488 STRING(QT_TRANSLATE_NOOP);
489 STRING(QT_TRANSLATE_NOOP3);
490 STRING(QT_TR_NOOP_UTF8);
491 STRING(QT_TRANSLATE_NOOP_UTF8);
492 STRING(QT_TRANSLATE_NOOP3_UTF8);
493 STRING(class);
494 // QTranslator::findMessage() has the same parameters as QApplication::translate()
495 STRING(findMessage);
496 STRING(friend);
497 STRING(namespace);
498 STRING(operator);
499 STRING(qtTrId);
500 STRING(return);
501 STRING(struct);
502 STRING(TR);
503 STRING(Tr);
504 STRING(tr);
505 STRING(trUtf8);
506 STRING(translate);
507 STRING(using);
508 
getToken()509 uint CppParser::getToken()
510 {
511   restart:
512     // Failing this assertion would mean losing the preallocated buffer.
513     Q_ASSERT(yyWord.isDetached());
514     yyWord.resize(0);
515 
516     while (yyCh != EOF) {
517         yyLineNo = yyCurLineNo;
518 
519         if (yyCh == '#' && yyAtNewline) {
520             /*
521               Early versions of lupdate complained about
522               unbalanced braces in the following code:
523 
524                   #ifdef ALPHA
525                       while (beta) {
526                   #else
527                       while (gamma) {
528                   #endif
529                           delta;
530                       }
531 
532               The code contains, indeed, two opening braces for
533               one closing brace; yet there's no reason to panic.
534 
535               The solution is to remember yyBraceDepth as it was
536               when #if, #ifdef or #ifndef was met, and to set
537               yyBraceDepth to that value when meeting #elif or
538               #else.
539             */
540             do {
541                 yyCh = getChar();
542             } while (isspace(yyCh) && yyCh != '\n');
543 
544             switch (yyCh) {
545             case 'd': // define
546                 // Skip over the name of the define to avoid it being interpreted as c++ code
547                 do { // Rest of "define"
548                     yyCh = getChar();
549                     if (yyCh == EOF)
550                         return Tok_Eof;
551                     if (yyCh == '\n')
552                         goto restart;
553                 } while (!isspace(yyCh));
554                 do { // Space beween "define" and macro name
555                     yyCh = getChar();
556                     if (yyCh == EOF)
557                         return Tok_Eof;
558                     if (yyCh == '\n')
559                         goto restart;
560                 } while (isspace(yyCh));
561                 do { // Macro name
562                     if (yyCh == '(') {
563                         // Argument list. Follows the name without a space, and no
564                         // paren nesting is possible.
565                         do {
566                             yyCh = getChar();
567                             if (yyCh == EOF)
568                                 return Tok_Eof;
569                             if (yyCh == '\n')
570                                 goto restart;
571                         } while (yyCh != ')');
572                         break;
573                     }
574                     yyCh = getChar();
575                     if (yyCh == EOF)
576                         return Tok_Eof;
577                     if (yyCh == '\n')
578                         goto restart;
579                 } while (!isspace(yyCh));
580                 do { // Shortcut the immediate newline case if no comments follow.
581                     yyCh = getChar();
582                     if (yyCh == EOF)
583                         return Tok_Eof;
584                     if (yyCh == '\n')
585                         goto restart;
586                 } while (isspace(yyCh));
587 
588                 saveState(&savedState);
589                 yyMinBraceDepth = yyBraceDepth;
590                 inDefine = true;
591                 goto restart;
592             case 'i':
593                 yyCh = getChar();
594                 if (yyCh == 'f') {
595                     // if, ifdef, ifndef
596                     yyIfdefStack.push(IfdefState(yyBracketDepth, yyBraceDepth, yyParenDepth));
597                     yyCh = getChar();
598                 } else if (yyCh == 'n') {
599                     // include
600                     do {
601                         yyCh = getChar();
602                     } while (yyCh != EOF && !isspace(yyCh));
603                     do {
604                         yyCh = getChar();
605                     } while (isspace(yyCh));
606                     int tChar;
607                     if (yyCh == '"')
608                         tChar = '"';
609                     else if (yyCh == '<')
610                         tChar = '>';
611                     else
612                         break;
613                     ushort *ptr = (ushort *)yyWord.unicode();
614                     forever {
615                         yyCh = getChar();
616                         if (yyCh == EOF || yyCh == '\n')
617                             break;
618                         if (yyCh == tChar) {
619                             yyCh = getChar();
620                             break;
621                         }
622                         *ptr++ = yyCh;
623                     }
624                     yyWord.resize(ptr - (ushort *)yyWord.unicode());
625                     return (tChar == '"') ? Tok_QuotedInclude : Tok_AngledInclude;
626                 }
627                 break;
628             case 'e':
629                 yyCh = getChar();
630                 if (yyCh == 'l') {
631                     // elif, else
632                     if (!yyIfdefStack.isEmpty()) {
633                         IfdefState &is = yyIfdefStack.top();
634                         if (is.elseLine != -1) {
635                             if (yyBracketDepth != is.bracketDepth1st
636                                 || yyBraceDepth != is.braceDepth1st
637                                 || yyParenDepth != is.parenDepth1st)
638                                 yyMsg(is.elseLine)
639                                     << qPrintable(LU::tr("Parenthesis/bracket/brace mismatch between "
640                                                          "#if and #else branches; using #if branch\n"));
641                         } else {
642                             is.bracketDepth1st = yyBracketDepth;
643                             is.braceDepth1st = yyBraceDepth;
644                             is.parenDepth1st = yyParenDepth;
645                             saveState(&is.state);
646                         }
647                         is.elseLine = yyLineNo;
648                         yyBracketDepth = is.bracketDepth;
649                         yyBraceDepth = is.braceDepth;
650                         yyParenDepth = is.parenDepth;
651                     }
652                     yyCh = getChar();
653                 } else if (yyCh == 'n') {
654                     // endif
655                     if (!yyIfdefStack.isEmpty()) {
656                         IfdefState is = yyIfdefStack.pop();
657                         if (is.elseLine != -1) {
658                             if (yyBracketDepth != is.bracketDepth1st
659                                 || yyBraceDepth != is.braceDepth1st
660                                 || yyParenDepth != is.parenDepth1st)
661                                 yyMsg(is.elseLine)
662                                     << qPrintable(LU::tr("Parenthesis/brace mismatch between "
663                                                          "#if and #else branches; using #if branch\n"));
664                             yyBracketDepth = is.bracketDepth1st;
665                             yyBraceDepth = is.braceDepth1st;
666                             yyParenDepth = is.parenDepth1st;
667                             loadState(&is.state);
668                         }
669                     }
670                     yyCh = getChar();
671                 }
672                 break;
673             }
674             // Optimization: skip over rest of preprocessor directive
675             do {
676                 if (yyCh == '/') {
677                     yyCh = getChar();
678                     if (yyCh == '/') {
679                         do {
680                             yyCh = getChar();
681                         } while (yyCh != EOF && yyCh != '\n');
682                         break;
683                     } else if (yyCh == '*') {
684                         bool metAster = false;
685 
686                         forever {
687                             yyCh = getChar();
688                             if (yyCh == EOF) {
689                                 yyMsg() << qPrintable(LU::tr("Unterminated C++ comment\n"));
690                                 break;
691                             }
692 
693                             if (yyCh == '*') {
694                                 metAster = true;
695                             } else if (metAster && yyCh == '/') {
696                                 yyCh = getChar();
697                                 break;
698                             } else {
699                                 metAster = false;
700                             }
701                         }
702                     }
703                 } else {
704                     yyCh = getChar();
705                 }
706             } while (yyCh != '\n' && yyCh != EOF);
707             yyCh = getChar();
708         } else if ((yyCh >= 'A' && yyCh <= 'Z') || (yyCh >= 'a' && yyCh <= 'z') || yyCh == '_') {
709             ushort *ptr = (ushort *)yyWord.unicode();
710             do {
711                 *ptr++ = yyCh;
712                 yyCh = getChar();
713             } while ((yyCh >= 'A' && yyCh <= 'Z') || (yyCh >= 'a' && yyCh <= 'z')
714                      || (yyCh >= '0' && yyCh <= '9') || yyCh == '_');
715             yyWord.resize(ptr - (ushort *)yyWord.unicode());
716 
717             //qDebug() << "IDENT: " << yyWord;
718 
719             switch (yyWord.unicode()[0].unicode()) {
720             case 'Q':
721                 if (yyWord == strQ_OBJECT)
722                     return Tok_Q_OBJECT;
723                 if (yyWord == strQ_DECLARE_TR_FUNCTIONS)
724                     return Tok_Q_DECLARE_TR_FUNCTIONS;
725                 if (yyWord == strQT_TR_NOOP)
726                     return Tok_tr;
727                 if (yyWord == strQT_TRID_NOOP)
728                     return Tok_trid;
729                 if (yyWord == strQT_TRANSLATE_NOOP)
730                     return Tok_translate;
731                 if (yyWord == strQT_TRANSLATE_NOOP3)
732                     return Tok_translate;
733                 if (yyWord == strQT_TR_NOOP_UTF8)
734                     return Tok_trUtf8;
735                 if (yyWord == strQT_TRANSLATE_NOOP_UTF8)
736                     return Tok_translateUtf8;
737                 if (yyWord == strQT_TRANSLATE_NOOP3_UTF8)
738                     return Tok_translateUtf8;
739                 break;
740             case 'T':
741                 // TR() for when all else fails
742                 if (yyWord == strTR || yyWord == strTr)
743                     return Tok_tr;
744                 break;
745             case 'c':
746                 if (yyWord == strclass)
747                     return Tok_class;
748                 break;
749             case 'f':
750                 /*
751                   QTranslator::findMessage() has the same parameters as
752                   QApplication::translate().
753                 */
754                 if (yyWord == strfindMessage)
755                     return Tok_translate;
756                 if (yyWord == strfriend)
757                     return Tok_friend;
758                 break;
759             case 'n':
760                 if (yyWord == strnamespace)
761                     return Tok_namespace;
762                 break;
763             case 'o':
764                 if (yyWord == stroperator) {
765                     // Operator overload declaration/definition.
766                     // We need to prevent those characters from confusing the followup
767                     // parsing. Actually using them does not add value, so just eat them.
768                     while (isspace(yyCh))
769                        yyCh = getChar();
770                     while (yyCh == '+' || yyCh == '-' || yyCh == '*' || yyCh == '/' || yyCh == '%'
771                            || yyCh == '=' || yyCh == '<' || yyCh == '>' || yyCh == '!'
772                            || yyCh == '&' || yyCh == '|' || yyCh == '~' || yyCh == '^'
773                            || yyCh == '[' || yyCh == ']')
774                         yyCh = getChar();
775                 }
776                 break;
777             case 'q':
778                 if (yyWord == strqtTrId)
779                     return Tok_trid;
780                 break;
781             case 'r':
782                 if (yyWord == strreturn)
783                     return Tok_return;
784                 break;
785             case 's':
786                 if (yyWord == strstruct)
787                     return Tok_class;
788                 break;
789             case 't':
790                 if (yyWord == strtr)
791                     return Tok_tr;
792                 if (yyWord == strtrUtf8)
793                     return Tok_trUtf8;
794                 if (yyWord == strtranslate)
795                     return Tok_translate;
796                 break;
797             case 'u':
798                 if (yyWord == strusing)
799                     return Tok_using;
800                 break;
801             }
802             return Tok_Ident;
803         } else {
804             switch (yyCh) {
805             case '\n':
806                 if (inDefine) {
807                     loadState(&savedState);
808                     prospectiveContext.clear();
809                     yyBraceDepth = yyMinBraceDepth;
810                     yyMinBraceDepth = 0;
811                     inDefine = false;
812                 }
813                 yyCh = getChar();
814                 break;
815             case '/':
816                 yyCh = getChar();
817                 if (yyCh == '/') {
818                     ushort *ptr = (ushort *)yyWord.unicode() + yyWord.length();
819                     do {
820                         yyCh = getChar();
821                         if (yyCh == EOF)
822                             break;
823                         *ptr++ = yyCh;
824                     } while (yyCh != '\n');
825                     yyWord.resize(ptr - (ushort *)yyWord.unicode());
826                 } else if (yyCh == '*') {
827                     bool metAster = false;
828                     ushort *ptr = (ushort *)yyWord.unicode() + yyWord.length();
829 
830                     forever {
831                         yyCh = getChar();
832                         if (yyCh == EOF) {
833                             yyMsg() << qPrintable(LU::tr("Unterminated C++ comment\n"));
834                             break;
835                         }
836                         *ptr++ = yyCh;
837 
838                         if (yyCh == '*')
839                             metAster = true;
840                         else if (metAster && yyCh == '/')
841                             break;
842                         else
843                             metAster = false;
844                     }
845                     yyWord.resize(ptr - (ushort *)yyWord.unicode() - 2);
846 
847                     yyCh = getChar();
848                 }
849                 return Tok_Comment;
850             case '"': {
851                 ushort *ptr = (ushort *)yyWord.unicode() + yyWord.length();
852                 yyCh = getChar();
853                 while (yyCh != EOF && yyCh != '\n' && yyCh != '"') {
854                     if (yyCh == '\\') {
855                         yyCh = getChar();
856                         if (yyCh == EOF || yyCh == '\n')
857                             break;
858                         *ptr++ = '\\';
859                     }
860                     *ptr++ = yyCh;
861                     yyCh = getChar();
862                 }
863                 yyWord.resize(ptr - (ushort *)yyWord.unicode());
864 
865                 if (yyCh != '"')
866                     yyMsg() << qPrintable(LU::tr("Unterminated C++ string\n"));
867                 else
868                     yyCh = getChar();
869                 return Tok_String;
870             }
871             case '-':
872                 yyCh = getChar();
873                 if (yyCh == '>') {
874                     yyCh = getChar();
875                     return Tok_Arrow;
876                 }
877                 break;
878             case ':':
879                 yyCh = getChar();
880                 if (yyCh == ':') {
881                     yyCh = getChar();
882                     return Tok_ColonColon;
883                 }
884                 return Tok_Colon;
885             // Incomplete: '<' might be part of '<=' or of template syntax.
886             // The main intent of not completely ignoring it is to break
887             // parsing of things like   std::cout << QObject::tr()  as
888             // context std::cout::QObject (see Task 161106)
889             case '=':
890                 yyCh = getChar();
891                 return Tok_Equals;
892             case '>':
893             case '<':
894                 yyCh = getChar();
895                 return Tok_Other;
896             case '\'':
897                 yyCh = getChar();
898                 if (yyCh == '\\')
899                     yyCh = getChar();
900 
901                 forever {
902                     if (yyCh == EOF || yyCh == '\n') {
903                         yyMsg() << "Unterminated C++ character\n";
904                         break;
905                     }
906                     yyCh = getChar();
907                     if (yyCh == '\'') {
908                         yyCh = getChar();
909                         break;
910                     }
911                 }
912                 break;
913             case '{':
914                 if (yyBraceDepth == 0)
915                     yyBraceLineNo = yyCurLineNo;
916                 yyBraceDepth++;
917                 yyCh = getChar();
918                 return Tok_LeftBrace;
919             case '}':
920                 if (yyBraceDepth == yyMinBraceDepth) {
921                     if (!inDefine)
922                         yyMsg(yyCurLineNo)
923                             << qPrintable(LU::tr("Excess closing brace in C++ code"
924                                                  " (or abuse of the C++ preprocessor)\n"));
925                     // Avoid things getting messed up even more
926                     yyCh = getChar();
927                     return Tok_Semicolon;
928                 }
929                 yyBraceDepth--;
930                 yyCh = getChar();
931                 return Tok_RightBrace;
932             case '(':
933                 if (yyParenDepth == 0)
934                     yyParenLineNo = yyCurLineNo;
935                 yyParenDepth++;
936                 yyCh = getChar();
937                 return Tok_LeftParen;
938             case ')':
939                 if (yyParenDepth == 0)
940                     yyMsg(yyCurLineNo)
941                         << qPrintable(LU::tr("Excess closing parenthesis in C++ code"
942                                              " (or abuse of the C++ preprocessor)\n"));
943                 else
944                     yyParenDepth--;
945                 yyCh = getChar();
946                 return Tok_RightParen;
947             case '[':
948                 if (yyBracketDepth == 0)
949                     yyBracketLineNo = yyCurLineNo;
950                 yyBracketDepth++;
951                 yyCh = getChar();
952                 return Tok_LeftBracket;
953             case ']':
954                 if (yyBracketDepth == 0)
955                     yyMsg(yyCurLineNo)
956                         << qPrintable(LU::tr("Excess closing bracket in C++ code"
957                                              " (or abuse of the C++ preprocessor)\n"));
958                 else
959                     yyBracketDepth--;
960                 yyCh = getChar();
961                 return Tok_RightBracket;
962             case ',':
963                 yyCh = getChar();
964                 return Tok_Comma;
965             case ';':
966                 yyCh = getChar();
967                 return Tok_Semicolon;
968             case '0':
969                 yyCh = getChar();
970                 if (yyCh == 'x') {
971                     do {
972                         yyCh = getChar();
973                     } while ((yyCh >= '0' && yyCh <= '9')
974                              || (yyCh >= 'a' && yyCh <= 'f') || (yyCh >= 'A' && yyCh <= 'F'));
975                     return Tok_Integer;
976                 }
977                 if (yyCh < '0' || yyCh > '9')
978                     return Tok_Null;
979                 // Fallthrough
980             case '1':
981             case '2':
982             case '3':
983             case '4':
984             case '5':
985             case '6':
986             case '7':
987             case '8':
988             case '9':
989                 do {
990                     yyCh = getChar();
991                 } while (yyCh >= '0' && yyCh <= '9');
992                 return Tok_Integer;
993             default:
994                 yyCh = getChar();
995                 break;
996             }
997         }
998     }
999     return Tok_Eof;
1000 }
1001 
1002 /*
1003   The second part of this source file are namespace/class related
1004   utilities for the third part.
1005 */
1006 
saveState(SavedState * state)1007 void CppParser::saveState(SavedState *state)
1008 {
1009     state->namespaces = namespaces;
1010     state->namespaceDepths = namespaceDepths;
1011     state->functionContext = functionContext;
1012     state->functionContextUnresolved = functionContextUnresolved;
1013     state->pendingContext = pendingContext;
1014 }
1015 
loadState(const SavedState * state)1016 void CppParser::loadState(const SavedState *state)
1017 {
1018     namespaces = state->namespaces;
1019     namespaceDepths = state->namespaceDepths;
1020     functionContext = state->functionContext;
1021     functionContextUnresolved = state->functionContextUnresolved;
1022     pendingContext = state->pendingContext;
1023 }
1024 
modifyNamespace(NamespaceList * namespaces,bool haveLast)1025 Namespace *CppParser::modifyNamespace(NamespaceList *namespaces, bool haveLast)
1026 {
1027     Namespace *pns, *ns = &results->rootNamespace;
1028     for (int i = 1; i < namespaces->count(); ++i) {
1029         pns = ns;
1030         if (!(ns = pns->children.value(namespaces->at(i)))) {
1031             do {
1032                 ns = new Namespace;
1033                 if (haveLast || i < namespaces->count() - 1)
1034                     if (const Namespace *ons = findNamespace(*namespaces, i + 1))
1035                         ns->classDef = ons->classDef;
1036                 pns->children.insert(namespaces->at(i), ns);
1037                 pns = ns;
1038             } while (++i < namespaces->count());
1039             break;
1040         }
1041     }
1042     return ns;
1043 }
1044 
stringifyNamespace(const NamespaceList & namespaces)1045 QString CppParser::stringifyNamespace(const NamespaceList &namespaces)
1046 {
1047     QString ret;
1048     for (int i = 1; i < namespaces.count(); ++i) {
1049         if (i > 1)
1050             ret += QLatin1String("::");
1051         ret += namespaces.at(i).value();
1052     }
1053     return ret;
1054 }
1055 
stringListifyNamespace(const NamespaceList & namespaces)1056 QStringList CppParser::stringListifyNamespace(const NamespaceList &namespaces)
1057 {
1058     QStringList ret;
1059     for (int i = 1; i < namespaces.count(); ++i)
1060         ret << namespaces.at(i).value();
1061     return ret;
1062 }
1063 
visitNamespace(const NamespaceList & namespaces,int nsCount,VisitNamespaceCallback callback,void * context,VisitRecorder & vr,const ParseResults * rslt) const1064 bool CppParser::visitNamespace(const NamespaceList &namespaces, int nsCount,
1065                                VisitNamespaceCallback callback, void *context,
1066                                VisitRecorder &vr, const ParseResults *rslt) const
1067 {
1068     const Namespace *ns = &rslt->rootNamespace;
1069     for (int i = 1; i < nsCount; ++i)
1070         if (!(ns = ns->children.value(namespaces.at(i))))
1071             goto supers;
1072     if ((this->*callback)(ns, context))
1073         return true;
1074 supers:
1075     foreach (const ParseResults *sup, rslt->includes)
1076         if (vr.tryVisit(sup->fileId)
1077             && visitNamespace(namespaces, nsCount, callback, context, vr, sup))
1078             return true;
1079     return false;
1080 }
1081 
visitNamespace(const NamespaceList & namespaces,int nsCount,VisitNamespaceCallback callback,void * context) const1082 bool CppParser::visitNamespace(const NamespaceList &namespaces, int nsCount,
1083                                VisitNamespaceCallback callback, void *context) const
1084 {
1085     VisitRecorder vr;
1086     return visitNamespace(namespaces, nsCount, callback, context, vr, results);
1087 }
1088 
stringListifySegments(const QList<HashString> & segments)1089 QStringList CppParser::stringListifySegments(const QList<HashString> &segments)
1090 {
1091     QStringList ret;
1092     for (int i = 0; i < segments.count(); ++i)
1093         ret << segments.at(i).value();
1094     return ret;
1095 }
1096 
1097 struct QualifyOneData {
QualifyOneDataQualifyOneData1098     QualifyOneData(const NamespaceList &ns, int nsc, const HashString &seg, NamespaceList *rslvd,
1099                    QSet<HashStringList> *visited)
1100         : namespaces(ns), nsCount(nsc), segment(seg), resolved(rslvd), visitedUsings(visited)
1101     {}
1102 
1103     const NamespaceList &namespaces;
1104     int nsCount;
1105     const HashString &segment;
1106     NamespaceList *resolved;
1107     QSet<HashStringList> *visitedUsings;
1108 };
1109 
qualifyOneCallbackOwn(const Namespace * ns,void * context) const1110 bool CppParser::qualifyOneCallbackOwn(const Namespace *ns, void *context) const
1111 {
1112     QualifyOneData *data = (QualifyOneData *)context;
1113     if (ns->children.contains(data->segment)) {
1114         *data->resolved = data->namespaces.mid(0, data->nsCount);
1115         *data->resolved << data->segment;
1116         return true;
1117     }
1118     QHash<HashString, NamespaceList>::ConstIterator nsai = ns->aliases.constFind(data->segment);
1119     if (nsai != ns->aliases.constEnd()) {
1120         const NamespaceList &nsl = *nsai;
1121         if (nsl.last().value().isEmpty()) { // Delayed alias resolution
1122             NamespaceList &nslIn = *const_cast<NamespaceList *>(&nsl);
1123             nslIn.removeLast();
1124             NamespaceList nslOut;
1125             if (!fullyQualify(data->namespaces, data->nsCount, nslIn, false, &nslOut, 0)) {
1126                 const_cast<Namespace *>(ns)->aliases.remove(data->segment);
1127                 return false;
1128             }
1129             nslIn = nslOut;
1130         }
1131         *data->resolved = nsl;
1132         return true;
1133     }
1134     return false;
1135 }
1136 
qualifyOneCallbackUsing(const Namespace * ns,void * context) const1137 bool CppParser::qualifyOneCallbackUsing(const Namespace *ns, void *context) const
1138 {
1139     QualifyOneData *data = (QualifyOneData *)context;
1140     foreach (const HashStringList &use, ns->usings)
1141         if (!data->visitedUsings->contains(use)) {
1142             data->visitedUsings->insert(use);
1143             if (qualifyOne(use.value(), use.value().count(), data->segment, data->resolved,
1144                            data->visitedUsings))
1145                 return true;
1146         }
1147     return false;
1148 }
1149 
qualifyOne(const NamespaceList & namespaces,int nsCnt,const HashString & segment,NamespaceList * resolved,QSet<HashStringList> * visitedUsings) const1150 bool CppParser::qualifyOne(const NamespaceList &namespaces, int nsCnt, const HashString &segment,
1151                            NamespaceList *resolved, QSet<HashStringList> *visitedUsings) const
1152 {
1153     QualifyOneData data(namespaces, nsCnt, segment, resolved, visitedUsings);
1154 
1155     if (visitNamespace(namespaces, nsCnt, &CppParser::qualifyOneCallbackOwn, &data))
1156         return true;
1157 
1158     return visitNamespace(namespaces, nsCnt, &CppParser::qualifyOneCallbackUsing, &data);
1159 }
1160 
qualifyOne(const NamespaceList & namespaces,int nsCnt,const HashString & segment,NamespaceList * resolved) const1161 bool CppParser::qualifyOne(const NamespaceList &namespaces, int nsCnt, const HashString &segment,
1162                            NamespaceList *resolved) const
1163 {
1164     QSet<HashStringList> visitedUsings;
1165 
1166     return qualifyOne(namespaces, nsCnt, segment, resolved, &visitedUsings);
1167 }
1168 
fullyQualify(const NamespaceList & namespaces,int nsCnt,const QList<HashString> & segments,bool isDeclaration,NamespaceList * resolved,QStringList * unresolved) const1169 bool CppParser::fullyQualify(const NamespaceList &namespaces, int nsCnt,
1170                              const QList<HashString> &segments, bool isDeclaration,
1171                              NamespaceList *resolved, QStringList *unresolved) const
1172 {
1173     int nsIdx;
1174     int initSegIdx;
1175 
1176     if (segments.first().value().isEmpty()) {
1177         // fully qualified
1178         if (segments.count() == 1) {
1179             resolved->clear();
1180             *resolved << HashString(QString());
1181             return true;
1182         }
1183         initSegIdx = 1;
1184         nsIdx = 0;
1185     } else {
1186         initSegIdx = 0;
1187         nsIdx = nsCnt - 1;
1188     }
1189 
1190     do {
1191         if (qualifyOne(namespaces, nsIdx + 1, segments[initSegIdx], resolved)) {
1192             int segIdx = initSegIdx;
1193             while (++segIdx < segments.count()) {
1194                 if (!qualifyOne(*resolved, resolved->count(), segments[segIdx], resolved)) {
1195                     if (unresolved)
1196                         *unresolved = stringListifySegments(segments.mid(segIdx));
1197                     return false;
1198                 }
1199             }
1200             return true;
1201         }
1202     } while (!isDeclaration && --nsIdx >= 0);
1203     resolved->clear();
1204     *resolved << HashString(QString());
1205     if (unresolved)
1206         *unresolved = stringListifySegments(segments.mid(initSegIdx));
1207     return false;
1208 }
1209 
fullyQualify(const NamespaceList & namespaces,const QList<HashString> & segments,bool isDeclaration,NamespaceList * resolved,QStringList * unresolved) const1210 bool CppParser::fullyQualify(const NamespaceList &namespaces,
1211                              const QList<HashString> &segments, bool isDeclaration,
1212                              NamespaceList *resolved, QStringList *unresolved) const
1213 {
1214     return fullyQualify(namespaces, namespaces.count(),
1215                         segments, isDeclaration, resolved, unresolved);
1216 }
1217 
fullyQualify(const NamespaceList & namespaces,const QString & quali,bool isDeclaration,NamespaceList * resolved,QStringList * unresolved) const1218 bool CppParser::fullyQualify(const NamespaceList &namespaces,
1219                              const QString &quali, bool isDeclaration,
1220                              NamespaceList *resolved, QStringList *unresolved) const
1221 {
1222     static QString strColons(QLatin1String("::"));
1223 
1224     QList<HashString> segments;
1225     foreach (const QString &str, quali.split(strColons)) // XXX slow, but needs to be fast(?)
1226         segments << HashString(str);
1227     return fullyQualify(namespaces, segments, isDeclaration, resolved, unresolved);
1228 }
1229 
findNamespaceCallback(const Namespace * ns,void * context) const1230 bool CppParser::findNamespaceCallback(const Namespace *ns, void *context) const
1231 {
1232     *((const Namespace **)context) = ns;
1233     return true;
1234 }
1235 
findNamespace(const NamespaceList & namespaces,int nsCount) const1236 const Namespace *CppParser::findNamespace(const NamespaceList &namespaces, int nsCount) const
1237 {
1238     const Namespace *ns = 0;
1239     if (nsCount == -1)
1240         nsCount = namespaces.count();
1241     visitNamespace(namespaces, nsCount, &CppParser::findNamespaceCallback, &ns);
1242     return ns;
1243 }
1244 
enterNamespace(NamespaceList * namespaces,const HashString & name)1245 void CppParser::enterNamespace(NamespaceList *namespaces, const HashString &name)
1246 {
1247     *namespaces << name;
1248     if (!findNamespace(*namespaces))
1249         modifyNamespace(namespaces, false);
1250 }
1251 
truncateNamespaces(NamespaceList * namespaces,int length)1252 void CppParser::truncateNamespaces(NamespaceList *namespaces, int length)
1253 {
1254     if (namespaces->count() > length)
1255         namespaces->erase(namespaces->begin() + length, namespaces->end());
1256 }
1257 
1258 /*
1259   Functions for processing include files.
1260 */
1261 
includeCycles()1262 IncludeCycleHash &CppFiles::includeCycles()
1263 {
1264     static IncludeCycleHash cycles;
1265 
1266     return cycles;
1267 }
1268 
translatedFiles()1269 TranslatorHash &CppFiles::translatedFiles()
1270 {
1271     static TranslatorHash tors;
1272 
1273     return tors;
1274 }
1275 
blacklistedFiles()1276 QSet<QString> &CppFiles::blacklistedFiles()
1277 {
1278     static QSet<QString> blacklisted;
1279 
1280     return blacklisted;
1281 }
1282 
getResults(const QString & cleanFile)1283 QSet<const ParseResults *> CppFiles::getResults(const QString &cleanFile)
1284 {
1285     IncludeCycle * const cycle = includeCycles().value(cleanFile);
1286 
1287     if (cycle)
1288         return cycle->results;
1289     else
1290         return QSet<const ParseResults *>();
1291 }
1292 
setResults(const QString & cleanFile,const ParseResults * results)1293 void CppFiles::setResults(const QString &cleanFile, const ParseResults *results)
1294 {
1295     IncludeCycle *cycle = includeCycles().value(cleanFile);
1296 
1297     if (!cycle) {
1298         cycle = new IncludeCycle;
1299         includeCycles().insert(cleanFile, cycle);
1300     }
1301 
1302     cycle->fileNames.insert(cleanFile);
1303     cycle->results.insert(results);
1304 }
1305 
getTranslator(const QString & cleanFile)1306 const Translator *CppFiles::getTranslator(const QString &cleanFile)
1307 {
1308     return translatedFiles().value(cleanFile);
1309 }
1310 
setTranslator(const QString & cleanFile,const Translator * tor)1311 void CppFiles::setTranslator(const QString &cleanFile, const Translator *tor)
1312 {
1313     translatedFiles().insert(cleanFile, tor);
1314 }
1315 
isBlacklisted(const QString & cleanFile)1316 bool CppFiles::isBlacklisted(const QString &cleanFile)
1317 {
1318     return blacklistedFiles().contains(cleanFile);
1319 }
1320 
setBlacklisted(const QString & cleanFile)1321 void CppFiles::setBlacklisted(const QString &cleanFile)
1322 {
1323     blacklistedFiles().insert(cleanFile);
1324 }
1325 
addIncludeCycle(const QSet<QString> & fileNames)1326 void CppFiles::addIncludeCycle(const QSet<QString> &fileNames)
1327 {
1328     IncludeCycle * const cycle = new IncludeCycle;
1329     cycle->fileNames = fileNames;
1330 
1331     QSet<IncludeCycle *> intersectingCycles;
1332     foreach (const QString &fileName, fileNames) {
1333         IncludeCycle *intersectingCycle = includeCycles().value(fileName);
1334 
1335         if (intersectingCycle && !intersectingCycles.contains(intersectingCycle)) {
1336             intersectingCycles.insert(intersectingCycle);
1337 
1338             cycle->fileNames.unite(intersectingCycle->fileNames);
1339             cycle->results.unite(intersectingCycle->results);
1340         }
1341     }
1342     qDeleteAll(intersectingCycles);
1343 
1344     foreach (const QString &fileName, cycle->fileNames)
1345         includeCycles().insert(fileName, cycle);
1346 }
1347 
isHeader(const QString & name)1348 static bool isHeader(const QString &name)
1349 {
1350     QString fileExt = QFileInfo(name).suffix();
1351     return fileExt.isEmpty() || fileExt.startsWith(QLatin1Char('h'), Qt::CaseInsensitive);
1352 }
1353 
processInclude(const QString & file,ConversionData & cd,const QStringList & includeStack,QSet<QString> & inclusions)1354 void CppParser::processInclude(const QString &file, ConversionData &cd, const QStringList &includeStack,
1355                                QSet<QString> &inclusions)
1356 {
1357     QString cleanFile = QDir::cleanPath(file);
1358 
1359     const int index = includeStack.indexOf(cleanFile);
1360     if (index != -1) {
1361         CppFiles::addIncludeCycle(includeStack.mid(index).toSet());
1362         return;
1363     }
1364 
1365     // If the #include is in any kind of namespace, has been blacklisted previously,
1366     // or is not a header file (stdc++ extensionless or *.h*), then really include
1367     // it. Otherwise it is safe to process it stand-alone and re-use the parsed
1368     // namespace data for inclusion into other files.
1369     bool isIndirect = false;
1370     if (namespaces.count() == 1 && functionContext.count() == 1
1371         && functionContextUnresolved.isEmpty() && pendingContext.isEmpty()
1372         && !CppFiles::isBlacklisted(cleanFile)
1373         && isHeader(cleanFile)) {
1374 
1375         QSet<const ParseResults *> res = CppFiles::getResults(cleanFile);
1376         if (!res.isEmpty()) {
1377             results->includes.unite(res);
1378             return;
1379         }
1380 
1381         isIndirect = true;
1382     }
1383 
1384     QFile f(cleanFile);
1385     if (!f.open(QIODevice::ReadOnly)) {
1386         yyMsg() << qPrintable(LU::tr("Cannot open %1: %2\n").arg(cleanFile, f.errorString()));
1387         return;
1388     }
1389 
1390     QTextStream ts(&f);
1391     ts.setCodec(yySourceCodec);
1392     ts.setAutoDetectUnicode(true);
1393 
1394     inclusions.insert(cleanFile);
1395     if (isIndirect) {
1396         CppParser parser;
1397         foreach (const QString &projectRoot, cd.m_projectRoots)
1398             if (cleanFile.startsWith(projectRoot)) {
1399                 parser.setTranslator(new Translator);
1400                 break;
1401             }
1402         parser.setInput(ts, cleanFile);
1403         QStringList stack = includeStack;
1404         stack << cleanFile;
1405         parser.parse(cd.m_defaultContext, cd, stack, inclusions);
1406         results->includes.insert(parser.recordResults(true));
1407     } else {
1408         CppParser parser(results);
1409         parser.namespaces = namespaces;
1410         parser.functionContext = functionContext;
1411         parser.functionContextUnresolved = functionContextUnresolved;
1412         parser.pendingContext = pendingContext;
1413         parser.setInput(ts, cleanFile);
1414         parser.setTranslator(tor);
1415         QStringList stack = includeStack;
1416         stack << cleanFile;
1417         parser.parseInternal(cd, stack, inclusions);
1418         // Avoid that messages obtained by direct scanning are used
1419         CppFiles::setBlacklisted(cleanFile);
1420     }
1421     inclusions.remove(cleanFile);
1422 }
1423 
1424 /*
1425   The third part of this source file is the parser. It accomplishes
1426   a very easy task: It finds all strings inside a tr() or translate()
1427   call, and possibly finds out the context of the call. It supports
1428   three cases: (1) the context is specified, as in
1429   FunnyDialog::tr("Hello") or translate("FunnyDialog", "Hello");
1430   (2) the call appears within an inlined function; (3) the call
1431   appears within a function defined outside the class definition.
1432 */
1433 
match(uint t)1434 bool CppParser::match(uint t)
1435 {
1436     bool matches = (yyTok == t);
1437     if (matches)
1438         yyTok = getToken();
1439     return matches;
1440 }
1441 
matchString(QString * s)1442 bool CppParser::matchString(QString *s)
1443 {
1444     bool matches = false;
1445     s->clear();
1446     forever {
1447         while (yyTok == Tok_Comment)
1448             yyTok = getToken();
1449         if (yyTok != Tok_String)
1450             return matches;
1451         matches = true;
1452         *s += yyWord;
1453         s->detach();
1454         yyTok = getToken();
1455     }
1456 }
1457 
1458 STRING(QApplication);
1459 STRING(QCoreApplication);
1460 STRING(UnicodeUTF8);
1461 STRING(DefaultCodec);
1462 STRING(CodecForTr);
1463 
matchEncoding(bool * utf8)1464 bool CppParser::matchEncoding(bool *utf8)
1465 {
1466     if (yyTok != Tok_Ident)
1467         return false;
1468     if (yyWord == strQApplication || yyWord == strQCoreApplication) {
1469         yyTok = getToken();
1470         if (yyTok == Tok_ColonColon)
1471             yyTok = getToken();
1472     }
1473     if (yyWord == strUnicodeUTF8) {
1474         *utf8 = true;
1475         yyTok = getToken();
1476         return true;
1477     }
1478     if (yyWord == strDefaultCodec || yyWord == strCodecForTr) {
1479         *utf8 = false;
1480         yyTok = getToken();
1481         return true;
1482     }
1483     return false;
1484 }
1485 
matchStringOrNull(QString * s)1486 bool CppParser::matchStringOrNull(QString *s)
1487 {
1488     return matchString(s) || match(Tok_Null);
1489 }
1490 
1491 /*
1492  * match any expression that can return a number, which can be
1493  * 1. Literal number (e.g. '11')
1494  * 2. simple identifier (e.g. 'm_count')
1495  * 3. simple function call (e.g. 'size()' )
1496  * 4. function call on an object (e.g. 'list.size()')
1497  * 5. function call on an object (e.g. 'list->size()')
1498  *
1499  * Other cases:
1500  * size(2,4)
1501  * list().size()
1502  * list(a,b).size(2,4)
1503  * etc...
1504  */
matchExpression()1505 bool CppParser::matchExpression()
1506 {
1507     if (match(Tok_Null) || match(Tok_Integer))
1508         return true;
1509 
1510     int parenlevel = 0;
1511     while (match(Tok_Ident) || parenlevel > 0) {
1512         if (yyTok == Tok_RightParen) {
1513             if (parenlevel == 0) break;
1514             --parenlevel;
1515             yyTok = getToken();
1516         } else if (yyTok == Tok_LeftParen) {
1517             yyTok = getToken();
1518             if (yyTok == Tok_RightParen) {
1519                 yyTok = getToken();
1520             } else {
1521                 ++parenlevel;
1522             }
1523         } else if (yyTok == Tok_Ident) {
1524             continue;
1525         } else if (yyTok == Tok_Arrow) {
1526             yyTok = getToken();
1527         } else if (parenlevel == 0) {
1528             return false;
1529         }
1530     }
1531     return true;
1532 }
1533 
transcode(const QString & str,bool utf8)1534 QString CppParser::transcode(const QString &str, bool utf8)
1535 {
1536     static const char tab[] = "abfnrtv";
1537     static const char backTab[] = "\a\b\f\n\r\t\v";
1538     // This function has to convert back to bytes, as C's \0* sequences work at that level.
1539     const QByteArray in = yyForceUtf8 ? str.toUtf8() : tor->codec()->fromUnicode(str);
1540     QByteArray out;
1541 
1542     out.reserve(in.length());
1543     for (int i = 0; i < in.length();) {
1544         uchar c = in[i++];
1545         if (c == '\\') {
1546             if (i >= in.length())
1547                 break;
1548             c = in[i++];
1549 
1550             if (c == '\n')
1551                 continue;
1552 
1553             if (c == 'x') {
1554                 QByteArray hex;
1555                 while (i < in.length() && isxdigit((c = in[i]))) {
1556                     hex += c;
1557                     i++;
1558                 }
1559                 out += hex.toUInt(0, 16);
1560             } else if (c >= '0' && c < '8') {
1561                 QByteArray oct;
1562                 int n = 0;
1563                 oct += c;
1564                 while (n < 2 && i < in.length() && (c = in[i]) >= '0' && c < '8') {
1565                     i++;
1566                     n++;
1567                     oct += c;
1568                 }
1569                 out += oct.toUInt(0, 8);
1570             } else {
1571                 const char *p = strchr(tab, c);
1572                 out += !p ? c : backTab[p - tab];
1573             }
1574         } else {
1575             out += c;
1576         }
1577     }
1578     return (utf8 || yyForceUtf8) ? QString::fromUtf8(out.constData(), out.length())
1579                                  : tor->codec()->toUnicode(out);
1580 }
1581 
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)1582 void CppParser::recordMessage(
1583     int line, const QString &context, const QString &text, const QString &comment,
1584     const QString &extracomment, const QString &msgid, const TranslatorMessage::ExtraData &extra,
1585     bool utf8, bool plural)
1586 {
1587     TranslatorMessage msg(
1588         transcode(context, utf8), transcode(text, utf8), transcode(comment, utf8), QString(),
1589         yyFileName, line, QStringList(),
1590         TranslatorMessage::Unfinished, plural);
1591     msg.setExtraComment(transcode(extracomment.simplified(), utf8));
1592     msg.setId(msgid);
1593     msg.setExtras(extra);
1594     if ((utf8 || yyForceUtf8) && !yyCodecIsUtf8 && msg.needs8Bit())
1595         msg.setUtf8(true);
1596     tor->append(msg);
1597 }
1598 
parse(const QString & initialContext,ConversionData & cd,const QStringList & includeStack,QSet<QString> & inclusions)1599 void CppParser::parse(const QString &initialContext, ConversionData &cd, const QStringList &includeStack,
1600                       QSet<QString> &inclusions)
1601 {
1602     if (tor)
1603         yyCodecIsUtf8 = (tor->codecName() == "UTF-8");
1604 
1605     namespaces << HashString();
1606     functionContext = namespaces;
1607     functionContextUnresolved = initialContext;
1608 
1609     parseInternal(cd, includeStack, inclusions);
1610 }
1611 
parseInternal(ConversionData & cd,const QStringList & includeStack,QSet<QString> & inclusions)1612 void CppParser::parseInternal(ConversionData &cd, const QStringList &includeStack, QSet<QString> &inclusions)
1613 {
1614     static QString strColons(QLatin1String("::"));
1615 
1616     QString context;
1617     QString text;
1618     QString comment;
1619     QString extracomment;
1620     QString msgid;
1621     QString sourcetext;
1622     TranslatorMessage::ExtraData extra;
1623     QString prefix;
1624 #ifdef DIAGNOSE_RETRANSLATABILITY
1625     QString functionName;
1626 #endif
1627     int line;
1628     bool utf8;
1629     bool yyTokColonSeen = false; // Start of c'tor's initializer list
1630 
1631     yyWord.reserve(yyInStr.size()); // Rather insane. That's because we do no length checking.
1632     yyInPtr = (const ushort *)yyInStr.unicode();
1633     yyCh = getChar();
1634     yyTok = getToken();
1635     while (yyTok != Tok_Eof) {
1636         // these are array indexing operations. we ignore them entirely
1637         // so they don't confuse our scoping of static initializers.
1638         // we enter the loop by either reading a left bracket or by an
1639         // #else popping the state.
1640         while (yyBracketDepth)
1641             yyTok = getToken();
1642         //qDebug() << "TOKEN: " << yyTok;
1643         switch (yyTok) {
1644         case Tok_QuotedInclude: {
1645             text = QDir(QFileInfo(yyFileName).absolutePath()).absoluteFilePath(yyWord);
1646             text.detach();
1647             if (QFileInfo(text).isFile()) {
1648                 processInclude(text, cd, includeStack, inclusions);
1649                 yyTok = getToken();
1650                 break;
1651             }
1652         }
1653         /* fall through */
1654         case Tok_AngledInclude: {
1655             QStringList cSources = cd.m_allCSources.values(yyWord);
1656             if (!cSources.isEmpty()) {
1657                 foreach (const QString &cSource, cSources)
1658                     processInclude(cSource, cd, includeStack, inclusions);
1659                 goto incOk;
1660             }
1661             foreach (const QString &incPath, cd.m_includePath) {
1662                 text = QDir(incPath).absoluteFilePath(yyWord);
1663                 text.detach();
1664                 if (QFileInfo(text).isFile()) {
1665                     processInclude(text, cd, includeStack, inclusions);
1666                     goto incOk;
1667                 }
1668             }
1669           incOk:
1670             yyTok = getToken();
1671             break;
1672         }
1673         case Tok_friend:
1674             yyTok = getToken();
1675             // These are forward declarations, so ignore them.
1676             if (yyTok == Tok_class)
1677                 yyTok = getToken();
1678             break;
1679         case Tok_class:
1680             yyTokColonSeen = false;
1681             /*
1682               Partial support for inlined functions.
1683             */
1684             yyTok = getToken();
1685             if (yyBraceDepth == namespaceDepths.count() && yyParenDepth == 0) {
1686                 QList<HashString> quali;
1687                 HashString fct;
1688                 do {
1689                     /*
1690                       This code should execute only once, but we play
1691                       safe with impure definitions such as
1692                       'class Q_EXPORT QMessageBox', in which case
1693                       'QMessageBox' is the class name, not 'Q_EXPORT'.
1694                     */
1695                     text = yyWord;
1696                     text.detach();
1697                     fct.setValue(text);
1698                     yyTok = getToken();
1699                 } while (yyTok == Tok_Ident);
1700                 while (yyTok == Tok_ColonColon) {
1701                     yyTok = getToken();
1702                     if (yyTok != Tok_Ident)
1703                         break; // Oops ...
1704                     quali << fct;
1705                     text = yyWord;
1706                     text.detach();
1707                     fct.setValue(text);
1708                     yyTok = getToken();
1709                 }
1710                 while (yyTok == Tok_Comment)
1711                     yyTok = getToken();
1712                 if (yyTok == Tok_Colon) {
1713                     // Skip any token until '{' since we might do things wrong if we find
1714                     // a '::' token here.
1715                     do {
1716                         yyTok = getToken();
1717                     } while (yyTok != Tok_LeftBrace && yyTok != Tok_Eof);
1718                 } else {
1719                     if (yyTok != Tok_LeftBrace) {
1720                         // Obviously a forward declaration. We skip those, as they
1721                         // don't create actually usable namespaces.
1722                         break;
1723                     }
1724                 }
1725 
1726                 if (!quali.isEmpty()) {
1727                     // Forward-declared class definitions can be namespaced.
1728                     NamespaceList nsl;
1729                     if (!fullyQualify(namespaces, quali, true, &nsl, 0)) {
1730                         yyMsg() << "Ignoring definition of undeclared qualified class\n";
1731                         break;
1732                     }
1733                     namespaceDepths.push(namespaces.count());
1734                     namespaces = nsl;
1735                 } else {
1736                     namespaceDepths.push(namespaces.count());
1737                 }
1738                 enterNamespace(&namespaces, fct);
1739 
1740                 functionContext = namespaces;
1741                 functionContextUnresolved.clear(); // Pointless
1742                 prospectiveContext.clear();
1743                 pendingContext.clear();
1744 
1745                 yyTok = getToken();
1746             }
1747             break;
1748         case Tok_namespace:
1749             yyTokColonSeen = false;
1750             yyTok = getToken();
1751             if (yyTok == Tok_Ident) {
1752                 text = yyWord;
1753                 text.detach();
1754                 HashString ns = HashString(text);
1755                 yyTok = getToken();
1756                 if (yyTok == Tok_LeftBrace) {
1757                     namespaceDepths.push(namespaces.count());
1758                     enterNamespace(&namespaces, ns);
1759 
1760                     functionContext = namespaces;
1761                     functionContextUnresolved.clear();
1762                     prospectiveContext.clear();
1763                     pendingContext.clear();
1764                     yyTok = getToken();
1765                 } else if (yyTok == Tok_Equals) {
1766                     // e.g. namespace Is = OuterSpace::InnerSpace;
1767                     QList<HashString> fullName;
1768                     yyTok = getToken();
1769                     if (yyTok == Tok_ColonColon)
1770                         fullName.append(HashString(QString()));
1771                     while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) {
1772                         if (yyTok == Tok_Ident) {
1773                             text = yyWord;
1774                             text.detach();
1775                             fullName.append(HashString(text));
1776                         }
1777                         yyTok = getToken();
1778                     }
1779                     if (fullName.isEmpty())
1780                         break;
1781                     fullName.append(HashString(QString())); // Mark as unresolved
1782                     modifyNamespace(&namespaces)->aliases[ns] = fullName;
1783                 }
1784             } else if (yyTok == Tok_LeftBrace) {
1785                 // Anonymous namespace
1786                 namespaceDepths.push(namespaces.count());
1787                 yyTok = getToken();
1788             }
1789             break;
1790         case Tok_using:
1791             yyTok = getToken();
1792             // XXX this should affect only the current scope, not the entire current namespace
1793             if (yyTok == Tok_namespace) {
1794                 QList<HashString> fullName;
1795                 yyTok = getToken();
1796                 if (yyTok == Tok_ColonColon)
1797                     fullName.append(HashString(QString()));
1798                 while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) {
1799                     if (yyTok == Tok_Ident) {
1800                         text = yyWord;
1801                         text.detach();
1802                         fullName.append(HashString(text));
1803                     }
1804                     yyTok = getToken();
1805                 }
1806                 NamespaceList nsl;
1807                 if (fullyQualify(namespaces, fullName, false, &nsl, 0))
1808                     modifyNamespace(&namespaces)->usings << HashStringList(nsl);
1809             } else {
1810                 QList<HashString> fullName;
1811                 if (yyTok == Tok_ColonColon)
1812                     fullName.append(HashString(QString()));
1813                 while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) {
1814                     if (yyTok == Tok_Ident) {
1815                         text = yyWord;
1816                         text.detach();
1817                         fullName.append(HashString(text));
1818                     }
1819                     yyTok = getToken();
1820                 }
1821                 if (fullName.isEmpty())
1822                     break;
1823                 // using-declarations cannot rename classes, so the last element of
1824                 // fullName is already the resolved name we actually want.
1825                 // As we do no resolution here, we'll collect useless usings of data
1826                 // members and methods as well. This is no big deal.
1827                 HashString &ns = fullName.last();
1828                 fullName.append(HashString(QString())); // Mark as unresolved
1829                 modifyNamespace(&namespaces)->aliases[ns] = fullName;
1830             }
1831             break;
1832         case Tok_tr:
1833         case Tok_trUtf8:
1834             if (!tor)
1835                 goto case_default;
1836             if (!sourcetext.isEmpty())
1837                 yyMsg() << qPrintable(LU::tr("//% cannot be used with tr() / QT_TR_NOOP(). Ignoring\n"));
1838             utf8 = (yyTok == Tok_trUtf8);
1839             line = yyLineNo;
1840             yyTok = getToken();
1841             if (match(Tok_LeftParen) && matchString(&text) && !text.isEmpty()) {
1842                 comment.clear();
1843                 bool plural = false;
1844 
1845                 if (match(Tok_RightParen)) {
1846                     // no comment
1847                 } else if (match(Tok_Comma) && matchStringOrNull(&comment)) {   //comment
1848                     if (match(Tok_RightParen)) {
1849                         // ok,
1850                     } else if (match(Tok_Comma)) {
1851                         plural = true;
1852                     }
1853                 }
1854                 if (!pendingContext.isEmpty() && !prefix.startsWith(strColons)) {
1855                     QStringList unresolved;
1856                     if (!fullyQualify(namespaces, pendingContext, true, &functionContext, &unresolved)) {
1857                         functionContextUnresolved = unresolved.join(strColons);
1858                         yyMsg() << qPrintable(LU::tr("Qualifying with unknown namespace/class %1::%2\n")
1859                                               .arg(stringifyNamespace(functionContext)).arg(unresolved.first()));
1860                     }
1861                     pendingContext.clear();
1862                 }
1863                 if (prefix.isEmpty()) {
1864                     if (functionContextUnresolved.isEmpty()) {
1865                         int idx = functionContext.length();
1866                         if (idx < 2) {
1867                             yyMsg() << qPrintable(LU::tr("tr() cannot be called without context\n"));
1868                             break;
1869                         }
1870                         Namespace *fctx;
1871                         while (!(fctx = findNamespace(functionContext, idx)->classDef)->hasTrFunctions) {
1872                             if (idx == 1) {
1873                                 context = stringifyNamespace(functionContext);
1874                                 fctx = findNamespace(functionContext)->classDef;
1875                                 if (!fctx->complained) {
1876                                     yyMsg() << qPrintable(LU::tr("Class '%1' lacks Q_OBJECT macro\n")
1877                                                          .arg(context));
1878                                     fctx->complained = true;
1879                                 }
1880                                 goto gotctx;
1881                             }
1882                             --idx;
1883                         }
1884                         if (fctx->trQualification.isEmpty()) {
1885                             context.clear();
1886                             for (int i = 1;;) {
1887                                 context += functionContext.at(i).value();
1888                                 if (++i == idx)
1889                                     break;
1890                                 context += strColons;
1891                             }
1892                             fctx->trQualification = context;
1893                         } else {
1894                             context = fctx->trQualification;
1895                         }
1896                     } else {
1897                         context = (stringListifyNamespace(functionContext)
1898                                    << functionContextUnresolved).join(strColons);
1899                     }
1900                 } else {
1901 #ifdef DIAGNOSE_RETRANSLATABILITY
1902                     int last = prefix.lastIndexOf(strColons);
1903                     QString className = prefix.mid(last == -1 ? 0 : last + 2);
1904                     if (!className.isEmpty() && className == functionName) {
1905                         yyMsg() << qPrintable(LU::tr("It is not recommended to call tr() from within a constructor '%1::%2'\n")
1906                                 .arg(className).arg(functionName));
1907                     }
1908 #endif
1909                     prefix.chop(2);
1910                     NamespaceList nsl;
1911                     QStringList unresolved;
1912                     if (fullyQualify(functionContext, prefix, false, &nsl, &unresolved)) {
1913                         Namespace *fctx = findNamespace(nsl)->classDef;
1914                         if (fctx->trQualification.isEmpty()) {
1915                             context = stringifyNamespace(nsl);
1916                             fctx->trQualification = context;
1917                         } else {
1918                             context = fctx->trQualification;
1919                         }
1920                         if (!fctx->hasTrFunctions && !fctx->complained) {
1921                             yyMsg() << qPrintable(LU::tr("Class '%1' lacks Q_OBJECT macro\n").arg(context));
1922                             fctx->complained = true;
1923                         }
1924                     } else {
1925                         context = (stringListifyNamespace(nsl) + unresolved).join(strColons);
1926                     }
1927                     prefix.clear();
1928                 }
1929 
1930               gotctx:
1931                 recordMessage(line, context, text, comment, extracomment, msgid, extra, utf8, plural);
1932             }
1933             sourcetext.clear(); // Will have warned about that already
1934             extracomment.clear();
1935             msgid.clear();
1936             extra.clear();
1937             break;
1938         case Tok_translateUtf8:
1939         case Tok_translate:
1940             if (!tor)
1941                 goto case_default;
1942             if (!sourcetext.isEmpty())
1943                 yyMsg() << qPrintable(LU::tr("//% cannot be used with translate() / QT_TRANSLATE_NOOP(). Ignoring\n"));
1944             utf8 = (yyTok == Tok_translateUtf8);
1945             line = yyLineNo;
1946             yyTok = getToken();
1947             if (match(Tok_LeftParen)
1948                 && matchString(&context)
1949                 && match(Tok_Comma)
1950                 && matchString(&text) && !text.isEmpty())
1951             {
1952                 comment.clear();
1953                 bool plural = false;
1954                 if (!match(Tok_RightParen)) {
1955                     // look for comment
1956                     if (match(Tok_Comma) && matchStringOrNull(&comment)) {
1957                         if (!match(Tok_RightParen)) {
1958                             // look for encoding
1959                             if (match(Tok_Comma)) {
1960                                 if (matchEncoding(&utf8)) {
1961                                     if (!match(Tok_RightParen)) {
1962                                         // look for the plural quantifier,
1963                                         // this can be a number, an identifier or
1964                                         // a function call,
1965                                         // so for simplicity we mark it as plural if
1966                                         // we know we have a comma instead of an
1967                                         // right parentheses.
1968                                         plural = match(Tok_Comma);
1969                                     }
1970                                 } else {
1971                                     // This can be a QTranslator::translate("context",
1972                                     // "source", "comment", n) plural translation
1973                                     if (matchExpression() && match(Tok_RightParen)) {
1974                                         plural = true;
1975                                     } else {
1976                                         break;
1977                                     }
1978                                 }
1979                             } else {
1980                                 break;
1981                             }
1982                         }
1983                     } else {
1984                         break;
1985                     }
1986                 }
1987                 recordMessage(line, context, text, comment, extracomment, msgid, extra, utf8, plural);
1988             }
1989             sourcetext.clear(); // Will have warned about that already
1990             extracomment.clear();
1991             msgid.clear();
1992             extra.clear();
1993             break;
1994         case Tok_trid:
1995             if (!tor)
1996                 goto case_default;
1997             if (!msgid.isEmpty())
1998                 yyMsg() << qPrintable(LU::tr("//= cannot be used with qtTrId() / QT_TRID_NOOP(). Ignoring\n"));
1999             //utf8 = false; // Maybe use //%% or something like that
2000             line = yyLineNo;
2001             yyTok = getToken();
2002             if (match(Tok_LeftParen) && matchString(&msgid) && !msgid.isEmpty()) {
2003                 bool plural = match(Tok_Comma);
2004                 recordMessage(line, QString(), sourcetext, QString(), extracomment,
2005                               msgid, extra, false, plural);
2006             }
2007             sourcetext.clear();
2008             extracomment.clear();
2009             msgid.clear();
2010             extra.clear();
2011             break;
2012         case Tok_Q_DECLARE_TR_FUNCTIONS:
2013             if (getMacroArgs()) {
2014                 Namespace *ns = modifyNamespace(&namespaces);
2015                 ns->hasTrFunctions = true;
2016                 ns->trQualification = yyWord;
2017                 ns->trQualification.detach();
2018             }
2019             yyTok = getToken();
2020             break;
2021         case Tok_Q_OBJECT:
2022             modifyNamespace(&namespaces)->hasTrFunctions = true;
2023             yyTok = getToken();
2024             break;
2025         case Tok_Ident:
2026             prefix += yyWord;
2027             prefix.detach();
2028             yyTok = getToken();
2029             if (yyTok != Tok_ColonColon) {
2030                 prefix.clear();
2031                 if (yyTok == Tok_Ident && !yyParenDepth)
2032                     prospectiveContext.clear();
2033             }
2034             break;
2035         case Tok_Comment: {
2036             if (!tor)
2037                 goto case_default;
2038             const QChar *ptr = yyWord.unicode();
2039             if (*ptr == QLatin1Char(':') && ptr[1].isSpace()) {
2040                 yyWord.remove(0, 2);
2041                 extracomment += yyWord;
2042                 extracomment.detach();
2043             } else if (*ptr == QLatin1Char('=') && ptr[1].isSpace()) {
2044                 yyWord.remove(0, 2);
2045                 msgid = yyWord.simplified();
2046                 msgid.detach();
2047             } else if (*ptr == QLatin1Char('~') && ptr[1].isSpace()) {
2048                 yyWord.remove(0, 2);
2049                 text = yyWord.trimmed();
2050                 int k = text.indexOf(QLatin1Char(' '));
2051                 if (k > -1)
2052                     extra.insert(text.left(k), text.mid(k + 1).trimmed());
2053                 text.clear();
2054             } else if (*ptr == QLatin1Char('%') && ptr[1].isSpace()) {
2055                 sourcetext.reserve(sourcetext.length() + yyWord.length() - 2);
2056                 ushort *ptr = (ushort *)sourcetext.data() + sourcetext.length();
2057                 int p = 2, c;
2058                 forever {
2059                     if (p >= yyWord.length())
2060                         break;
2061                     c = yyWord.unicode()[p++].unicode();
2062                     if (isspace(c))
2063                         continue;
2064                     if (c != '"') {
2065                         yyMsg() << qPrintable(LU::tr("Unexpected character in meta string\n"));
2066                         break;
2067                     }
2068                     forever {
2069                         if (p >= yyWord.length()) {
2070                           whoops:
2071                             yyMsg() << qPrintable(LU::tr("Unterminated meta string\n"));
2072                             break;
2073                         }
2074                         c = yyWord.unicode()[p++].unicode();
2075                         if (c == '"')
2076                             break;
2077                         if (c == '\\') {
2078                             if (p >= yyWord.length())
2079                                 goto whoops;
2080                             c = yyWord.unicode()[p++].unicode();
2081                             if (c == '\n')
2082                                 goto whoops;
2083                             *ptr++ = '\\';
2084                         }
2085                         *ptr++ = c;
2086                     }
2087                 }
2088                 sourcetext.resize(ptr - (ushort *)sourcetext.data());
2089             } else {
2090                 const ushort *uc = (const ushort *)yyWord.unicode(); // Is zero-terminated
2091                 int idx = 0;
2092                 ushort c;
2093                 while ((c = uc[idx]) == ' ' || c == '\t' || c == '\n')
2094                     ++idx;
2095                 if (!memcmp(uc + idx, MagicComment.unicode(), MagicComment.length() * 2)) {
2096                     idx += MagicComment.length();
2097                     comment = QString::fromRawData(yyWord.unicode() + idx,
2098                                                    yyWord.length() - idx).simplified();
2099                     int k = comment.indexOf(QLatin1Char(' '));
2100                     if (k == -1) {
2101                         context = comment;
2102                     } else {
2103                         context = comment.left(k);
2104                         comment.remove(0, k + 1);
2105                         TranslatorMessage msg(
2106                                 transcode(context, false), QString(),
2107                                 transcode(comment, false), QString(),
2108                                 yyFileName, yyLineNo, QStringList(),
2109                                 TranslatorMessage::Finished, false);
2110                         msg.setExtraComment(transcode(extracomment.simplified(), false));
2111                         extracomment.clear();
2112                         tor->append(msg);
2113                         tor->setExtras(extra);
2114                         extra.clear();
2115                     }
2116                 }
2117             }
2118             yyTok = getToken();
2119             break;
2120         }
2121         case Tok_Arrow:
2122             yyTok = getToken();
2123             if (yyTok == Tok_tr || yyTok == Tok_trUtf8)
2124                 yyMsg() << qPrintable(LU::tr("Cannot invoke tr() like this\n"));
2125             break;
2126         case Tok_ColonColon:
2127             if (yyBraceDepth == namespaceDepths.count() && yyParenDepth == 0 && !yyTokColonSeen)
2128                 prospectiveContext = prefix;
2129             prefix += strColons;
2130             yyTok = getToken();
2131 #ifdef DIAGNOSE_RETRANSLATABILITY
2132             if (yyTok == Tok_Ident && yyBraceDepth == namespaceDepths.count() && yyParenDepth == 0) {
2133                 functionName = yyWord;
2134                 functionName.detach();
2135             }
2136 #endif
2137             break;
2138         case Tok_RightBrace:
2139             if (yyBraceDepth + 1 == namespaceDepths.count()) // class or namespace
2140                 truncateNamespaces(&namespaces, namespaceDepths.pop());
2141             if (yyBraceDepth == namespaceDepths.count()) {
2142                 // function, class or namespace
2143                 if (!yyBraceDepth && !directInclude) {
2144                     truncateNamespaces(&functionContext, 1);
2145                     functionContextUnresolved = cd.m_defaultContext;
2146                 } else {
2147                     functionContext = namespaces;
2148                     functionContextUnresolved.clear();
2149                 }
2150                 pendingContext.clear();
2151             }
2152             // fallthrough
2153         case Tok_Semicolon:
2154             prospectiveContext.clear();
2155             prefix.clear();
2156             if (!sourcetext.isEmpty() || !extracomment.isEmpty() || !msgid.isEmpty() || !extra.isEmpty()) {
2157                 yyMsg() << qPrintable(LU::tr("Discarding unconsumed meta data\n"));
2158                 sourcetext.clear();
2159                 extracomment.clear();
2160                 msgid.clear();
2161                 extra.clear();
2162             }
2163             yyTokColonSeen = false;
2164             yyTok = getToken();
2165             break;
2166         case Tok_Colon:
2167             if (!prospectiveContext.isEmpty()
2168                 && yyBraceDepth == namespaceDepths.count() && yyParenDepth == 0)
2169                 pendingContext = prospectiveContext;
2170             yyTokColonSeen = true;
2171             yyTok = getToken();
2172             break;
2173         case Tok_LeftBrace:
2174             if (!prospectiveContext.isEmpty()
2175                 && yyBraceDepth == namespaceDepths.count() + 1 && yyParenDepth == 0)
2176                 pendingContext = prospectiveContext;
2177             // fallthrough
2178         case Tok_LeftParen:
2179         case Tok_RightParen:
2180             yyTokColonSeen = false;
2181             yyTok = getToken();
2182             break;
2183         default:
2184             if (!yyParenDepth)
2185                 prospectiveContext.clear();
2186             // fallthrough
2187         case Tok_Equals: // for static initializers; other cases make no difference
2188         case Tok_RightBracket: // ignoring indexing; same reason
2189         case_default:
2190             yyTok = getToken();
2191             break;
2192         }
2193     }
2194 
2195     if (yyBraceDepth != 0)
2196         yyMsg(yyBraceLineNo)
2197             << qPrintable(LU::tr("Unbalanced opening brace in C++ code"
2198                                  " (or abuse of the C++ preprocessor)\n"));
2199     else if (yyParenDepth != 0)
2200         yyMsg(yyParenLineNo)
2201             << qPrintable(LU::tr("Unbalanced opening parenthesis in C++ code"
2202                                  " (or abuse of the C++ preprocessor)\n"));
2203     else if (yyBracketDepth != 0)
2204         yyMsg(yyBracketLineNo)
2205             << qPrintable(LU::tr("Unbalanced opening bracket in C++ code"
2206                                  " (or abuse of the C++ preprocessor)\n"));
2207 }
2208 
recordResults(bool isHeader)2209 const ParseResults *CppParser::recordResults(bool isHeader)
2210 {
2211     if (tor) {
2212         if (tor->messageCount()) {
2213             CppFiles::setTranslator(yyFileName, tor);
2214         } else {
2215             delete tor;
2216             tor = 0;
2217         }
2218     }
2219     if (isHeader) {
2220         const ParseResults *pr;
2221         if (!tor && results->includes.count() == 1
2222             && results->rootNamespace.children.isEmpty()
2223             && results->rootNamespace.aliases.isEmpty()
2224             && results->rootNamespace.usings.isEmpty()) {
2225             // This is a forwarding header. Slash it.
2226             pr = *results->includes.begin();
2227             delete results;
2228         } else {
2229             results->fileId = nextFileId++;
2230             pr = results;
2231         }
2232         CppFiles::setResults(yyFileName, pr);
2233         return pr;
2234     } else {
2235         delete results;
2236         return 0;
2237     }
2238 }
2239 
2240 /*
2241   Fetches tr() calls in C++ code in UI files (inside "<function>"
2242   tag). This mechanism is obsolete.
2243 */
fetchtrInlinedCpp(const QString & in,Translator & translator,const QString & context)2244 void fetchtrInlinedCpp(const QString &in, Translator &translator, const QString &context)
2245 {
2246     CppParser parser;
2247     parser.setInput(in);
2248     ConversionData cd;
2249     QSet<QString> inclusions;
2250     parser.setTranslator(&translator);
2251     parser.parse(context, cd, QStringList(), inclusions);
2252     parser.deleteResults();
2253 }
2254 
loadCPP(Translator & translator,const QStringList & filenames,ConversionData & cd)2255 void loadCPP(Translator &translator, const QStringList &filenames, ConversionData &cd)
2256 {
2257     QByteArray codecName = cd.m_codecForSource.isEmpty()
2258                             ? translator.codecName() : cd.m_codecForSource;
2259     QTextCodec *codec = QTextCodec::codecForName(codecName);
2260 
2261     foreach (const QString &filename, filenames) {
2262         if (!CppFiles::getResults(filename).isEmpty() || CppFiles::isBlacklisted(filename))
2263             continue;
2264 
2265         QFile file(filename);
2266         if (!file.open(QIODevice::ReadOnly)) {
2267             cd.appendError(LU::tr("Cannot open %1: %2").arg(filename, file.errorString()));
2268             continue;
2269         }
2270 
2271         CppParser parser;
2272         QTextStream ts(&file);
2273         ts.setCodec(codec);
2274         ts.setAutoDetectUnicode(true);
2275         parser.setInput(ts, filename);
2276         if (cd.m_outputCodec.isEmpty() && ts.codec()->name() == "UTF-16")
2277             translator.setCodecName("System");
2278         Translator *tor = new Translator;
2279         tor->setCodecName(translator.codecName());
2280         parser.setTranslator(tor);
2281         QSet<QString> inclusions;
2282         parser.parse(cd.m_defaultContext, cd, QStringList(), inclusions);
2283         parser.recordResults(isHeader(filename));
2284     }
2285 
2286     foreach (const QString &filename, filenames)
2287         if (!CppFiles::isBlacklisted(filename))
2288             if (const Translator *tor = CppFiles::getTranslator(filename))
2289                 foreach (const TranslatorMessage &msg, tor->messages())
2290                     translator.extend(msg);
2291 }
2292 
2293 QT_END_NAMESPACE
2294