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