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

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