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

source code of qttools/src/linguist/lupdate/cpp.cpp