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