Warning: That file was not part of the compilation database. It may have many parsing errors.
1 | /**************************************************************************** |
---|---|
2 | ** |
3 | ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
5 | ** |
6 | ** This file is part of the tools applications of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and Digia. For licensing terms and |
14 | ** conditions see http://qt.digia.com/licensing. For further information |
15 | ** use the contact form at http://qt.digia.com/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 2.1 requirements |
23 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
24 | ** |
25 | ** In addition, as a special exception, Digia gives you certain additional |
26 | ** rights. These rights are described in the Digia Qt LGPL Exception |
27 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
28 | ** |
29 | ** GNU General Public License Usage |
30 | ** Alternatively, this file may be used under the terms of the GNU |
31 | ** General Public License version 3.0 as published by the Free Software |
32 | ** Foundation and appearing in the file LICENSE.GPL included in the |
33 | ** packaging of this file. Please review the following information to |
34 | ** ensure the GNU General Public License version 3.0 requirements will be |
35 | ** met: http://www.gnu.org/copyleft/gpl.html. |
36 | ** |
37 | ** |
38 | ** $QT_END_LICENSE$ |
39 | ** |
40 | ****************************************************************************/ |
41 | |
42 | /* |
43 | cppcodeparser.cpp |
44 | */ |
45 | |
46 | #include <qfile.h> |
47 | |
48 | #include <stdio.h> |
49 | #include <errno.h> |
50 | #include <qdebug.h> |
51 | #include "codechunk.h" |
52 | #include "config.h" |
53 | #include "cppcodeparser.h" |
54 | #include "tokenizer.h" |
55 | #include "tree.h" |
56 | |
57 | QT_BEGIN_NAMESPACE |
58 | |
59 | /* qmake ignore Q_OBJECT */ |
60 | |
61 | #define COMMAND_CLASS Doc::alias("class") |
62 | #define COMMAND_CONTENTSPAGE Doc::alias("contentspage") |
63 | #define COMMAND_ENUM Doc::alias("enum") |
64 | #define COMMAND_EXAMPLE Doc::alias("example") |
65 | #define COMMAND_EXTERNALPAGE Doc::alias("externalpage") |
66 | #define COMMAND_FILE Doc::alias("file") // ### don't document |
67 | #define COMMAND_FN Doc::alias("fn") |
68 | #define COMMAND_GROUP Doc::alias("group") |
69 | #define COMMAND_HEADERFILE Doc::alias("headerfile") |
70 | #define COMMAND_INDEXPAGE Doc::alias("indexpage") |
71 | #define COMMAND_INHEADERFILE Doc::alias("inheaderfile") // ### don't document |
72 | #define COMMAND_MACRO Doc::alias("macro") |
73 | #define COMMAND_MODULE Doc::alias("module") // ### don't document |
74 | #define COMMAND_NAMESPACE Doc::alias("namespace") |
75 | #define COMMAND_OVERLOAD Doc::alias("overload") |
76 | #define COMMAND_NEXTPAGE Doc::alias("nextpage") |
77 | #define COMMAND_PAGE Doc::alias("page") |
78 | #define COMMAND_PREVIOUSPAGE Doc::alias("previouspage") |
79 | #define COMMAND_PROPERTY Doc::alias("property") |
80 | #define COMMAND_REIMP Doc::alias("reimp") |
81 | #define COMMAND_RELATES Doc::alias("relates") |
82 | #define COMMAND_SERVICE Doc::alias("service") |
83 | #define COMMAND_STARTPAGE Doc::alias("startpage") |
84 | #define COMMAND_TYPEDEF Doc::alias("typedef") |
85 | #define COMMAND_VARIABLE Doc::alias("variable") |
86 | |
87 | #ifdef QDOC_QML |
88 | #define COMMAND_QMLCLASS Doc::alias("qmlclass") |
89 | #define COMMAND_QMLPROPERTY Doc::alias("qmlproperty") |
90 | #define COMMAND_QMLATTACHEDPROPERTY Doc::alias("qmlattachedproperty") |
91 | #define COMMAND_QMLINHERITS Doc::alias("inherits") |
92 | #define COMMAND_QMLSIGNAL Doc::alias("qmlsignal") |
93 | #define COMMAND_QMLATTACHEDSIGNAL Doc::alias("qmlattachedsignal") |
94 | #define COMMAND_QMLMETHOD Doc::alias("qmlmethod") |
95 | #define COMMAND_QMLATTACHEDMETHOD Doc::alias("qmlattachedmethod") |
96 | #define COMMAND_QMLDEFAULT Doc::alias("default") |
97 | #define COMMAND_QMLBASICTYPE Doc::alias("qmlbasictype") |
98 | #endif |
99 | |
100 | #define COMMAND_AUDIENCE Doc::alias("audience") |
101 | #define COMMAND_CATEGORY Doc::alias("category") |
102 | #define COMMAND_PRODNAME Doc::alias("prodname") |
103 | #define COMMAND_COMPONENT Doc::alias("component") |
104 | #define COMMAND_AUTHOR Doc::alias("author") |
105 | #define COMMAND_PUBLISHER Doc::alias("publisher") |
106 | #define COMMAND_COPYRYEAR Doc::alias("copyryear") |
107 | #define COMMAND_COPYRHOLDER Doc::alias("copyrholder") |
108 | #define COMMAND_PERMISSIONS Doc::alias("permissions") |
109 | #define COMMAND_LIFECYCLEVERSION Doc::alias("lifecycleversion") |
110 | #define COMMAND_LIFECYCLEWSTATUS Doc::alias("lifecyclestatus") |
111 | #define COMMAND_LICENSEYEAR Doc::alias("licenseyear") |
112 | #define COMMAND_LICENSENAME Doc::alias("licensename") |
113 | #define COMMAND_LICENSEDESCRIPTION Doc::alias("licensedescription") |
114 | #define COMMAND_RELEASEDATE Doc::alias("releasedate") |
115 | |
116 | QStringList CppCodeParser::exampleFiles; |
117 | QStringList CppCodeParser::exampleDirs; |
118 | |
119 | static void extractPageLinkAndDesc(const QString &arg, |
120 | QString *link, |
121 | QString *desc) |
122 | { |
123 | QRegExp bracedRegExp("\\{([^{}]*)\\}(?:\\{([^{}]*)\\})?"); |
124 | |
125 | if (bracedRegExp.exactMatch(arg)) { |
126 | *link = bracedRegExp.cap(1); |
127 | *desc = bracedRegExp.cap(2); |
128 | if (desc->isEmpty()) |
129 | *desc = *link; |
130 | } |
131 | else { |
132 | int spaceAt = arg.indexOf(" "); |
133 | if (arg.contains(".html") && spaceAt != -1) { |
134 | *link = arg.left(spaceAt).trimmed(); |
135 | *desc = arg.mid(spaceAt).trimmed(); |
136 | } |
137 | else { |
138 | *link = arg; |
139 | *desc = arg; |
140 | } |
141 | } |
142 | } |
143 | |
144 | static void setLink(Node *node, Node::LinkType linkType, const QString &arg) |
145 | { |
146 | QString link; |
147 | QString desc; |
148 | extractPageLinkAndDesc(arg, &link, &desc); |
149 | node->setLink(linkType, link, desc); |
150 | } |
151 | |
152 | /* |
153 | This is used for fuzzy matching only, which in turn is only used |
154 | for Qt Jambi. |
155 | */ |
156 | static QString cleanType(const QString &type, const Tree *tree) |
157 | { |
158 | QString result = type; |
159 | result.replace("qlonglong", "long long"); |
160 | result.replace("qulonglong", "unsigned long long"); |
161 | result.replace("qreal", "double"); |
162 | result.replace(QRegExp("\\bu(int|short|char|long)\\b"), "unsigned \\1"); |
163 | result.replace("QRgb", "unsigned int"); |
164 | result.replace(" >", ">"); |
165 | result.remove(" const[]"); |
166 | result.replace("QStringList<QString>", "QStringList"); |
167 | result.replace("qint8", "char"); |
168 | result.replace("qint16", "short"); |
169 | result.replace("qint32", "int"); |
170 | result.replace("qint64", "long long"); |
171 | result.replace("quint8", "unsigned char"); |
172 | result.replace("quint16", "unsigned short"); |
173 | result.replace("quint32", "unsigned int"); |
174 | result.replace("quint64", "unsigned long long"); |
175 | |
176 | if (result.contains("QFlags")) { |
177 | QRegExp regExp("QFlags<(((?:[^<>]+::)*)([^<>:]+))>"); |
178 | int pos = 0; |
179 | while ((pos = result.indexOf(regExp, pos)) != -1) { |
180 | // we assume that the path for the associated enum |
181 | // is the same as for the flag typedef |
182 | QStringList path = regExp.cap(2).split("::", |
183 | QString::SkipEmptyParts); |
184 | const EnumNode *enume = static_cast<const EnumNode *>( |
185 | tree->findNode(QStringList(path) << regExp.cap(3), |
186 | Node::Enum)); |
187 | if (enume && enume->flagsType()) |
188 | result.replace(pos, regExp.matchedLength(), |
189 | (QStringList(path) << enume->flagsType()->name()).join("::")); |
190 | ++pos; |
191 | } |
192 | } |
193 | if (result.contains("::")) { |
194 | // remove needless (and needful) class prefixes |
195 | QRegExp regExp("[A-Za-z0-9_]+::"); |
196 | result.replace(regExp, ""); |
197 | } |
198 | return result; |
199 | } |
200 | |
201 | /*! |
202 | The constructor initializes some regular expressions |
203 | and calls reset(). |
204 | */ |
205 | CppCodeParser::CppCodeParser() |
206 | : varComment("/\\*\\s*([a-zA-Z_0-9]+)\\s*\\*/"), sep( "(?:<[^>]+>)?::") |
207 | { |
208 | reset(0); |
209 | } |
210 | |
211 | /*! |
212 | The destructor is trivial. |
213 | */ |
214 | CppCodeParser::~CppCodeParser() |
215 | { |
216 | // nothing. |
217 | } |
218 | |
219 | /*! |
220 | The constructor initializes a map of special node types |
221 | for identifying important nodes. And it initializes |
222 | some filters for identifying certain kinds of files. |
223 | */ |
224 | void CppCodeParser::initializeParser(const Config &config) |
225 | { |
226 | CodeParser::initializeParser(config); |
227 | |
228 | nodeTypeMap.insert(COMMAND_NAMESPACE, Node::Namespace); |
229 | nodeTypeMap.insert(COMMAND_CLASS, Node::Class); |
230 | nodeTypeMap.insert(COMMAND_SERVICE, Node::Class); |
231 | nodeTypeMap.insert(COMMAND_ENUM, Node::Enum); |
232 | nodeTypeMap.insert(COMMAND_TYPEDEF, Node::Typedef); |
233 | nodeTypeMap.insert(COMMAND_PROPERTY, Node::Property); |
234 | nodeTypeMap.insert(COMMAND_VARIABLE, Node::Variable); |
235 | |
236 | exampleFiles = config.getStringList(CONFIG_EXAMPLES); |
237 | exampleDirs = config.getStringList(CONFIG_EXAMPLEDIRS); |
238 | QStringList exampleFilePatterns = config.getStringList( |
239 | CONFIG_EXAMPLES + Config::dot + CONFIG_FILEEXTENSIONS); |
240 | |
241 | if (!exampleFilePatterns.isEmpty()) |
242 | exampleNameFilter = exampleFilePatterns.join(" "); |
243 | else |
244 | exampleNameFilter = "*.cpp *.h *.js *.xq *.svg *.xml *.ui"; |
245 | |
246 | QStringList exampleImagePatterns = config.getStringList( |
247 | CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS); |
248 | |
249 | if (!exampleImagePatterns.isEmpty()) |
250 | exampleImageFilter = exampleImagePatterns.join(" "); |
251 | else |
252 | exampleImageFilter = "*.png"; |
253 | } |
254 | |
255 | /*! |
256 | Clear the map of common node types and call |
257 | the same function in the base class. |
258 | */ |
259 | void CppCodeParser::terminateParser() |
260 | { |
261 | nodeTypeMap.clear(); |
262 | CodeParser::terminateParser(); |
263 | } |
264 | |
265 | /*! |
266 | Returns "Cpp". |
267 | */ |
268 | QString CppCodeParser::language() |
269 | { |
270 | return "Cpp"; |
271 | } |
272 | |
273 | /*! |
274 | Returns a list of extensions for header files. |
275 | */ |
276 | QStringList CppCodeParser::headerFileNameFilter() |
277 | { |
278 | return QStringList() << "*.ch"<< "*.h"<< "*.h++"<< "*.hh"<< "*.hpp"<< "*.hxx"; |
279 | } |
280 | |
281 | /*! |
282 | Returns a list of extensions for source files, i.e. not |
283 | header files. |
284 | */ |
285 | QStringList CppCodeParser::sourceFileNameFilter() |
286 | { |
287 | return QStringList() << "*.c++"<< "*.cc"<< "*.cpp"<< "*.cxx"<< "*.mm"; |
288 | } |
289 | |
290 | /*! |
291 | Parse the C++ header file identified by \a filePath |
292 | and add the parsed contents to the big \a tree. The |
293 | \a location is used for reporting errors. |
294 | */ |
295 | void CppCodeParser::parseHeaderFile(const Location& location, |
296 | const QString& filePath, |
297 | Tree *tree) |
298 | { |
299 | QFile in(filePath); |
300 | if (!in.open(QIODevice::ReadOnly)) { |
301 | location.error(tr("Cannot open C++ header file '%1'").arg(filePath)); |
302 | return; |
303 | } |
304 | |
305 | reset(tree); |
306 | Location fileLocation(filePath); |
307 | Tokenizer fileTokenizer(fileLocation, in); |
308 | tokenizer = &fileTokenizer; |
309 | readToken(); |
310 | matchDeclList(tree->root()); |
311 | if (!fileTokenizer.version().isEmpty()) |
312 | tree->setVersion(fileTokenizer.version()); |
313 | in.close(); |
314 | |
315 | if (fileLocation.fileName() == "qiterator.h") |
316 | parseQiteratorDotH(location, filePath); |
317 | } |
318 | |
319 | /*! |
320 | Get ready to parse the C++ cpp file identified by \a filePath |
321 | and add its parsed contents to the big \a tree. \a location is |
322 | used for reporting errors. |
323 | |
324 | Call matchDocsAndStuff() to do all the parsing and tree building. |
325 | */ |
326 | void CppCodeParser::parseSourceFile(const Location& location, |
327 | const QString& filePath, |
328 | Tree *tree) |
329 | { |
330 | QFile in(filePath); |
331 | if (!in.open(QIODevice::ReadOnly)) { |
332 | location.error(tr("Cannot open C++ source file '%1' (%2)").arg(filePath).arg(strerror(errno))); |
333 | return; |
334 | } |
335 | |
336 | reset(tree); |
337 | Location fileLocation(filePath); |
338 | Tokenizer fileTokenizer(fileLocation, in); |
339 | tokenizer = &fileTokenizer; |
340 | readToken(); |
341 | usedNamespaces.clear(); |
342 | matchDocsAndStuff(); |
343 | in.close(); |
344 | } |
345 | |
346 | /*! |
347 | This is called after all the header files have been parsed. |
348 | I think the most important thing it does is resolve class |
349 | inheritance links in the tree. But it also initializes a |
350 | bunch of stuff. |
351 | */ |
352 | void CppCodeParser::doneParsingHeaderFiles(Tree *tree) |
353 | { |
354 | tree->resolveInheritance(); |
355 | |
356 | QMapIterator<QString, QString> i(sequentialIteratorClasses); |
357 | while (i.hasNext()) { |
358 | i.next(); |
359 | instantiateIteratorMacro(i.key(), |
360 | i.value(), |
361 | sequentialIteratorDefinition, |
362 | tree); |
363 | } |
364 | i = mutableSequentialIteratorClasses; |
365 | while (i.hasNext()) { |
366 | i.next(); |
367 | instantiateIteratorMacro(i.key(), |
368 | i.value(), |
369 | mutableSequentialIteratorDefinition, |
370 | tree); |
371 | } |
372 | i = associativeIteratorClasses; |
373 | while (i.hasNext()) { |
374 | i.next(); |
375 | instantiateIteratorMacro(i.key(), |
376 | i.value(), |
377 | associativeIteratorDefinition, |
378 | tree); |
379 | } |
380 | i = mutableAssociativeIteratorClasses; |
381 | while (i.hasNext()) { |
382 | i.next(); |
383 | instantiateIteratorMacro(i.key(), |
384 | i.value(), |
385 | mutableAssociativeIteratorDefinition, |
386 | tree); |
387 | } |
388 | sequentialIteratorDefinition.clear(); |
389 | mutableSequentialIteratorDefinition.clear(); |
390 | associativeIteratorDefinition.clear(); |
391 | mutableAssociativeIteratorDefinition.clear(); |
392 | sequentialIteratorClasses.clear(); |
393 | mutableSequentialIteratorClasses.clear(); |
394 | associativeIteratorClasses.clear(); |
395 | mutableAssociativeIteratorClasses.clear(); |
396 | } |
397 | |
398 | /*! |
399 | This is called after all the source files (i.e., not the |
400 | header files) have been parsed. It traverses the tree to |
401 | resolve property links, normalize overload signatures, and |
402 | do other housekeeping of the tree. |
403 | */ |
404 | void CppCodeParser::doneParsingSourceFiles(Tree *tree) |
405 | { |
406 | tree->root()->makeUndocumentedChildrenInternal(); |
407 | tree->root()->normalizeOverloads(); |
408 | tree->fixInheritance(); |
409 | tree->resolveProperties(); |
410 | } |
411 | |
412 | /*! |
413 | This function searches the \a tree to find a FunctionNode |
414 | for a function with the signature \a synopsis. If the |
415 | \a relative node is provided, the search begins there. If |
416 | \a fuzzy is true, base classes are searched. The function |
417 | node is returned, if found. |
418 | */ |
419 | const FunctionNode *CppCodeParser::findFunctionNode(const QString& synopsis, |
420 | Tree *tree, |
421 | Node *relative, |
422 | bool fuzzy) |
423 | { |
424 | QStringList parentPath; |
425 | FunctionNode *clone; |
426 | FunctionNode *func = 0; |
427 | int flags = fuzzy ? int(Tree::SearchBaseClasses) : 0; |
428 | |
429 | reset(tree); |
430 | if (makeFunctionNode(synopsis, &parentPath, &clone)) { |
431 | func = tree->findFunctionNode(parentPath, clone, relative, flags); |
432 | |
433 | /* |
434 | This is necessary because Roberto's parser resolves typedefs. |
435 | */ |
436 | if (!func && fuzzy) { |
437 | func = tre->findFunctionNode(parentPath + |
438 | QStringList(clone->name()), |
439 | relative, |
440 | flags); |
441 | if (!func && clone->name().contains( |
442 | QStringList path = parentPath; |
443 | path << clone->name().split( |
444 | func = tre->findFunctionNode(path, relative, flags); |
445 | } |
446 | |
447 | if (func) { |
448 | NodeList overloads = func->parent()->overloads(func->name()); |
449 | NodeList candidates; |
450 | for (int i = 0; i < overloads.count(); ++i) { |
451 | FunctionNode *overload = static_cast<FunctionNode *>(overloads.at(i)); |
452 | if (overload->status() != Node::Compat |
453 | && overload->parameters().count() == clone->parameters().count() |
454 | && !overload->isConst() == !clone->isConst()) |
455 | candidates << overload; |
456 | } |
457 | if (candidates.count() == 0) |
458 | return 0; |
459 | |
460 | /* |
461 | There's only one function with the correct number |
462 | of parameters. That must be the one. |
463 | */ |
464 | if (candidates.count() == 1) |
465 | return static_cast<FunctionNode *>(candidates.first()); |
466 | |
467 | overloads = candidates; |
468 | candidates.clear(); |
469 | for (int i = 0; i < overloads.count(); ++i) { |
470 | FunctionNode *overload = static_cast<FunctionNode *>(overloads.at(i)); |
471 | QList<Parameter> params1 = overload->parameters(); |
472 | QList<Parameter> params2 = clone->parameters(); |
473 | |
474 | int j; |
475 | for (j = 0; j < params1.count(); ++j) { |
476 | if (!params2.at(j).name().startsWith(params1.at(j).name())) |
477 | break; |
478 | } |
479 | if (j == params1.count()) |
480 | candidates << overload; |
481 | } |
482 | |
483 | /* |
484 | There are several functions with the correct |
485 | parameter count, but only one has the correct |
486 | parameter names. |
487 | */ |
488 | if (candidates.count() == 1) |
489 | return static_cast<FunctionNode *>(candidates.first()); |
490 | |
491 | candidates.clear(); |
492 | for (int i = 0; i < overloads.count(); ++i) { |
493 | FunctionNode *overload = static_cast<FunctionNode *>(overloads.at(i)); |
494 | QList<Parameter> params1 = overload->parameters(); |
495 | QList<Parameter> params2 = clone->parameters(); |
496 | |
497 | int j; |
498 | for (j = 0; j < params1.count(); ++j) { |
499 | if (params1.at(j).rightType() != params2.at(j).rightType()) |
500 | break; |
501 | |
502 | if (cleanType(params1.at(j).leftType(), tree) |
503 | != cleanType(params2.at(j).leftType(), tree)) |
504 | break; |
505 | } |
506 | if (j == params1.count()) |
507 | candidates << overload; |
508 | } |
509 | |
510 | |
511 | /* |
512 | There are several functions with the correct |
513 | parameter count, but only one has the correct |
514 | types, loosely compared. |
515 | */ |
516 | if (candidates.count() == 1) |
517 | return static_cast<FunctionNode *>(candidates.first()); |
518 | |
519 | return 0; |
520 | } |
521 | } |
522 | delete clone; |
523 | } |
524 | return func; |
525 | } |
526 | |
527 | /*! |
528 | Returns the set of strings reopresenting the topic commands. |
529 | */ |
530 | QSet<QString> CppCodeParser::topicCommands() |
531 | { |
532 | return QSet<QString>() << COMMAND_CLASS |
533 | << COMMAND_ENUM |
534 | << COMMAND_EXAMPLE |
535 | << COMMAND_EXTERNALPAGE |
536 | << COMMAND_FILE |
537 | << COMMAND_FN |
538 | << COMMAND_GROUP |
539 | << COMMAND_HEADERFILE |
540 | << COMMAND_MACRO |
541 | << COMMAND_MODULE |
542 | << COMMAND_NAMESPACE |
543 | << COMMAND_PAGE |
544 | << COMMAND_PROPERTY |
545 | << COMMAND_SERVICE |
546 | << COMMAND_TYPEDEF |
547 | #ifdef QDOC_QML |
548 | << COMMAND_VARIABLE |
549 | << COMMAND_QMLCLASS |
550 | << COMMAND_QMLPROPERTY |
551 | << COMMAND_QMLATTACHEDPROPERTY |
552 | << COMMAND_QMLSIGNAL |
553 | << COMMAND_QMLATTACHEDSIGNAL |
554 | << COMMAND_QMLMETHOD |
555 | << COMMAND_QMLATTACHEDMETHOD |
556 | << COMMAND_QMLBASICTYPE; |
557 | #else |
558 | << COMMAND_VARIABLE; |
559 | #endif |
560 | } |
561 | |
562 | /*! |
563 | Process the topic \a command in context \a doc with argument \a arg. |
564 | */ |
565 | Node *CppCodeParser::processTopicCommand(const Doc& doc, |
566 | const QString& command, |
567 | const QString& arg) |
568 | { |
569 | if (command == COMMAND_FN) { |
570 | QStringList parentPath; |
571 | FunctionNode *func = 0; |
572 | FunctionNode *clone = 0; |
573 | |
574 | if (!makeFunctionNode(arg, &parentPath, &clone) && |
575 | !makeFunctionNode("void "+ arg, &parentPath, &clone)) { |
576 | doc.location().warning(tr("Invalid syntax in '\\%1'") |
577 | .arg(COMMAND_FN)); |
578 | } |
579 | else { |
580 | if (!usedNamespaces.isEmpty()) { |
581 | foreach (const QString &usedNamespace, usedNamespaces) { |
582 | QStringList newPath = usedNamespace.split("::") + parentPath; |
583 | func = tre->findFunctionNode(newPath, clone); |
584 | if (func) |
585 | break; |
586 | } |
587 | } |
588 | // Search the root namespace if no match was found. |
589 | if (func == 0) |
590 | func = tre->findFunctionNode(parentPath, clone); |
591 | |
592 | if (func == 0) { |
593 | if (parentPath.isEmpty() && !lastPath.isEmpty()) |
594 | func = tre->findFunctionNode(lastPath, clone); |
595 | if (func == 0) { |
596 | doc.location().warning(tr("Cannot find '%1' in '\\%2'") |
597 | .arg(clone->name() + "(...)") |
598 | .arg(COMMAND_FN), |
599 | tr("I cannot find any function of that name with the " |
600 | "specified signature. Make sure that the signature " |
601 | "is identical to the declaration, including 'const' " |
602 | "qualifiers.")); |
603 | } |
604 | else { |
605 | doc.location().warning(tr("Missing '%1::' for '%2' in '\\%3'") |
606 | .arg(lastPath.join("::")) |
607 | .arg(clone->name() + "()") |
608 | .arg(COMMAND_FN)); |
609 | } |
610 | } |
611 | else { |
612 | lastPath = parentPath; |
613 | } |
614 | if (func) { |
615 | func->borrowParameterNames(clone); |
616 | func->setParentPath(clone->parentPath()); |
617 | } |
618 | delete clone; |
619 | } |
620 | return func; |
621 | } |
622 | else if (command == COMMAND_MACRO) { |
623 | QStringList parentPath; |
624 | FunctionNode *func = 0; |
625 | |
626 | if (makeFunctionNode(arg, &parentPath, &func, tre->root())) { |
627 | if (!parentPath.isEmpty()) { |
628 | doc.location().warning(tr("Invalid syntax in '\\%1'") |
629 | .arg(COMMAND_MACRO)); |
630 | delete func; |
631 | func = 0; |
632 | } |
633 | else { |
634 | func->setMetaness(FunctionNode::MacroWithParams); |
635 | QList<Parameter> params = func->parameters(); |
636 | for (int i = 0; i < params.size(); ++i) { |
637 | Parameter ¶m = params[i]; |
638 | if (param.name().isEmpty() && !param.leftType().isEmpty() |
639 | && param.leftType() != "...") |
640 | param = Parameter("", "", param.leftType()); |
641 | } |
642 | func->setParameters(params); |
643 | } |
644 | return func; |
645 | } |
646 | else if (QRegExp("[A-Za-z_][A-Za-z0-9_]+").exactMatch(arg)) { |
647 | func = new FunctionNode(tre->root(), arg); |
648 | func->setAccess(Node::Public); |
649 | func->setLocation(doc.location()); |
650 | func->setMetaness(FunctionNode::MacroWithoutParams); |
651 | } |
652 | else { |
653 | doc.location().warning(tr("Invalid syntax in '\\%1'") |
654 | .arg(COMMAND_MACRO)); |
655 | |
656 | } |
657 | return func; |
658 | } |
659 | else if (nodeTypeMap.contains(command)) { |
660 | /* |
661 | The command was neither "fn" nor "macro" . |
662 | */ |
663 | // ### split(" ") hack is there to support header file syntax |
664 | QStringList paths = arg.split(" "); |
665 | QStringList path = paths[0].split("::"); |
666 | Node *node = 0; |
667 | if (!usedNamespaces.isEmpty()) { |
668 | foreach (const QString &usedNamespace, usedNamespaces) { |
669 | QStringList newPath = usedNamespace.split("::") + path; |
670 | node = tre->findNode(newPath, nodeTypeMap[command]); |
671 | if (node) { |
672 | path = newPath; |
673 | break; |
674 | } |
675 | } |
676 | } |
677 | // Search the root namespace if no match was found. |
678 | if (node == 0) |
679 | node = tre->findNode(path, nodeTypeMap[command]); |
680 | |
681 | if (node == 0) { |
682 | doc.location().warning(tr("Cannot find '%1' specified with '\\%2' in any header file") |
683 | .arg(arg).arg(command)); |
684 | lastPath = path; |
685 | |
686 | } |
687 | else if (command == COMMAND_SERVICE) { |
688 | // If the command is "\service", then we need to tag the |
689 | // class with the actual service name. |
690 | QStringList args = arg.split(" "); |
691 | if (args.size() > 1) { |
692 | ClassNode *cnode = static_cast<ClassNode *>(node); |
693 | cnode->setServiceName(args[1]); |
694 | cnode->setHideFromMainList(true); |
695 | } |
696 | } |
697 | else if (node->isInnerNode()) { |
698 | if (path.size() > 1) { |
699 | path.pop_back(); |
700 | usedNamespaces.insert(path.join("::")); |
701 | } |
702 | } |
703 | return node; |
704 | } |
705 | else if (command == COMMAND_EXAMPLE) { |
706 | ExampleNode* en = new ExampleNode(tre->root(), arg); |
707 | createExampleFileNodes(en); |
708 | return en; |
709 | } |
710 | else if (command == COMMAND_EXTERNALPAGE) { |
711 | return new FakeNode(tre->root(), arg, Node::ExternalPage); |
712 | } |
713 | else if (command == COMMAND_FILE) { |
714 | return new FakeNode(tre->root(), arg, Node::File); |
715 | } |
716 | else if (command == COMMAND_GROUP) { |
717 | return new FakeNode(tre->root(), arg, Node::Group); |
718 | } |
719 | else if (command == COMMAND_HEADERFILE) { |
720 | return new FakeNode(tre->root(), arg, Node::HeaderFile); |
721 | } |
722 | else if (command == COMMAND_MODULE) { |
723 | return new FakeNode(tre->root(), arg, Node::Module); |
724 | } |
725 | else if (command == COMMAND_PAGE) { |
726 | return new FakeNode(tre->root(), arg, Node::Page); |
727 | } |
728 | #ifdef QDOC_QML |
729 | else if (command == COMMAND_QMLCLASS) { |
730 | const ClassNode* classNode = 0; |
731 | QStringList names = arg.split(" "); |
732 | if (names.size() > 1) { |
733 | Node* n = tre->findNode(names[1].split("::"),Node::Class); |
734 | if (n) |
735 | classNode = static_cast<const ClassNode*>(n); |
736 | } |
737 | if (names[0].startsWith("Qt")) |
738 | return new QmlClassNode(tre->root(), QLatin1String("QML:")+names[0], classNode); |
739 | else |
740 | return new QmlClassNode(tre->root(), names[0], classNode); |
741 | } |
742 | else if (command == COMMAND_QMLBASICTYPE) { |
743 | return new QmlBasicTypeNode(tre->root(), arg); |
744 | } |
745 | else if ((command == COMMAND_QMLSIGNAL) || |
746 | (command == COMMAND_QMLMETHOD) || |
747 | (command == COMMAND_QMLATTACHEDSIGNAL) || |
748 | (command == COMMAND_QMLATTACHEDMETHOD)) { |
749 | QString element; |
750 | QString type; |
751 | QmlClassNode* qmlClass = 0; |
752 | if (splitQmlMethodArg(doc,arg,type,element)) { |
753 | if (element.startsWith(QLatin1String("Qt"))) |
754 | element = QLatin1String("QML:") + element; |
755 | Node* n = tre->findNode(QStringList(element),Node::Fake); |
756 | if (n && n->subType() == Node::QmlClass) { |
757 | qmlClass = static_cast<QmlClassNode*>(n); |
758 | if (command == COMMAND_QMLSIGNAL) |
759 | return makeFunctionNode(doc,arg,qmlClass,Node::QmlSignal,false,COMMAND_QMLSIGNAL); |
760 | else if (command == COMMAND_QMLATTACHEDSIGNAL) |
761 | return makeFunctionNode(doc,arg,qmlClass,Node::QmlSignal,true,COMMAND_QMLATTACHEDSIGNAL); |
762 | else if (command == COMMAND_QMLMETHOD) |
763 | return makeFunctionNode(doc,arg,qmlClass,Node::QmlMethod,false,COMMAND_QMLMETHOD); |
764 | else if (command == COMMAND_QMLATTACHEDMETHOD) |
765 | return makeFunctionNode(doc,arg,qmlClass,Node::QmlMethod,true,COMMAND_QMLATTACHEDMETHOD); |
766 | else |
767 | return 0; // never get here. |
768 | } |
769 | } |
770 | } |
771 | #endif |
772 | return 0; |
773 | } |
774 | |
775 | #ifdef QDOC_QML |
776 | |
777 | /*! |
778 | A QML property argument has the form... |
779 | |
780 | <type> <element>::<name> |
781 | |
782 | This function splits the argument into those three |
783 | parts, sets \a type, \a element, and \a name, |
784 | and returns true. If any of the parts isn't found, |
785 | a qdoc warning is output and false is returned. |
786 | */ |
787 | bool CppCodeParser::splitQmlPropertyArg(const Doc& doc, |
788 | const QString& arg, |
789 | QString& type, |
790 | QString& element, |
791 | QString& name) |
792 | { |
793 | QStringList blankSplit = arg.split(" "); |
794 | if (blankSplit.size() > 1) { |
795 | type = blankSplit[0]; |
796 | QStringList colonSplit(blankSplit[1].split("::")); |
797 | if (colonSplit.size() > 1) { |
798 | element = colonSplit[0]; |
799 | name = colonSplit[1]; |
800 | return true; |
801 | } |
802 | else |
803 | doc.location().warning(tr("Missing parent QML element name")); |
804 | } |
805 | else |
806 | doc.location().warning(tr("Missing property type")); |
807 | return false; |
808 | } |
809 | |
810 | /*! |
811 | A QML signal or method argument has the form... |
812 | |
813 | <type> <element>::<name>(<param>, <param>, ...) |
814 | |
815 | This function splits the argument into those two |
816 | parts, sets \a element, and \a name, and returns |
817 | true. If either of the parts isn't found, a debug |
818 | message is output and false is returned. |
819 | */ |
820 | bool CppCodeParser::splitQmlMethodArg(const Doc& doc, |
821 | const QString& arg, |
822 | QString& type, |
823 | QString& element) |
824 | { |
825 | QStringList colonSplit(arg.split("::")); |
826 | if (colonSplit.size() > 1) { |
827 | QStringList blankSplit = colonSplit[0].split(" "); |
828 | if (blankSplit.size() > 1) { |
829 | type = blankSplit[0]; |
830 | element = blankSplit[1]; |
831 | } |
832 | else { |
833 | type = QString(""); |
834 | element = colonSplit[0]; |
835 | } |
836 | return true; |
837 | } |
838 | else |
839 | doc.location().warning(tr("Missing parent QML element or method signature")); |
840 | return false; |
841 | } |
842 | |
843 | /*! |
844 | Process the topic \a command group with arguments \a args. |
845 | |
846 | Currently, this function is called only for \e{qmlproperty} |
847 | and \e{qmlattachedproperty}. |
848 | */ |
849 | Node *CppCodeParser::processTopicCommandGroup(const Doc& doc, |
850 | const QString& command, |
851 | const QStringList& args) |
852 | { |
853 | QmlPropGroupNode* qmlPropGroup = 0; |
854 | if ((command == COMMAND_QMLPROPERTY) || |
855 | (command == COMMAND_QMLATTACHEDPROPERTY)) { |
856 | QString type; |
857 | QString element; |
858 | QString property; |
859 | bool attached = (command == COMMAND_QMLATTACHEDPROPERTY); |
860 | QStringList::ConstIterator arg = args.begin(); |
861 | if (splitQmlPropertyArg(doc,(*arg),type,element,property)) { |
862 | Node* n = tre->findNode(QStringList(element),Node::Fake); |
863 | if (n && n->subType() == Node::QmlClass) { |
864 | QmlClassNode* qmlClass = static_cast<QmlClassNode*>(n); |
865 | if (qmlClass) |
866 | qmlPropGroup = new QmlPropGroupNode(qmlClass, |
867 | property, |
868 | attached); |
869 | } |
870 | } |
871 | if (qmlPropGroup) { |
872 | const ClassNode *correspondingClass = static_cast<const QmlClassNode*>(qmlPropGroup->parent())->classNode(); |
873 | QmlPropertyNode *qmlPropNode = new QmlPropertyNode(qmlPropGroup,property,type,attached); |
874 | |
875 | const PropertyNode *correspondingProperty = 0; |
876 | if (correspondingClass) { |
877 | correspondingProperty = qmlPropNode->correspondingProperty(tre); |
878 | } |
879 | if (correspondingProperty) { |
880 | bool writableList = type.startsWith("list") && correspondingProperty->dataType().endsWith( |
881 | qmlPropNode->setWritable(writableList || correspondingProperty->isWritable()); |
882 | } |
883 | ++arg; |
884 | while (arg != args.end()) { |
885 | if (splitQmlPropertyArg(doc,(*arg),type,element,property)) { |
886 | QmlPropertyNode* qmlPropNode = new QmlPropertyNode(qmlPropGroup, |
887 | property, |
888 | type, |
889 | attached); |
890 | if (correspondingProperty) { |
891 | bool writableList = type.startsWith("list") && correspondingProperty->dataType().endsWith( |
892 | qmlPropNode->setWritable(writableList || correspondingProperty->isWritable()); |
893 | } |
894 | } |
895 | ++arg; |
896 | } |
897 | } |
898 | } |
899 | return qmlPropGroup; |
900 | } |
901 | #endif |
902 | |
903 | /*! |
904 | Returns the set of strings representing the common metacommands |
905 | plus some other metacommands. |
906 | */ |
907 | QSet<QString> CppCodeParser::otherMetaCommands() |
908 | { |
909 | return commonMetaCommands() << COMMAND_INHEADERFILE |
910 | << COMMAND_OVERLOAD |
911 | << COMMAND_REIMP |
912 | << COMMAND_RELATES |
913 | << COMMAND_CONTENTSPAGE |
914 | << COMMAND_NEXTPAGE |
915 | << COMMAND_PREVIOUSPAGE |
916 | << COMMAND_INDEXPAGE |
917 | #ifdef QDOC_QML |
918 | << COMMAND_STARTPAGE |
919 | << COMMAND_QMLINHERITS |
920 | << COMMAND_QMLDEFAULT; |
921 | #else |
922 | << COMMAND_STARTPAGE; |
923 | #endif |
924 | } |
925 | |
926 | /*! |
927 | Process the metacommand \a command in the context of the |
928 | \a node associated with the topic command and the \a doc. |
929 | \a arg is the argument to the metacommand. |
930 | */ |
931 | void CppCodeParser::processOtherMetaCommand(const Doc& doc, |
932 | const QString& command, |
933 | const QString& arg, |
934 | Node *node) |
935 | { |
936 | if (command == COMMAND_INHEADERFILE) { |
937 | if (node != 0 && node->isInnerNode()) { |
938 | ((InnerNode *) node)->addInclude(arg); |
939 | } |
940 | else { |
941 | doc.location().warning(tr("Ignored '\\%1'") |
942 | .arg(COMMAND_INHEADERFILE)); |
943 | } |
944 | } |
945 | else if (command == COMMAND_OVERLOAD) { |
946 | if (node != 0 && node->type() == Node::Function) { |
947 | ((FunctionNode *) node)->setOverload(true); |
948 | } |
949 | else { |
950 | doc.location().warning(tr("Ignored '\\%1'") |
951 | .arg(COMMAND_OVERLOAD)); |
952 | } |
953 | } |
954 | else if (command == COMMAND_REIMP) { |
955 | if (node != 0 && node->type() == Node::Function) { |
956 | FunctionNode *func = (FunctionNode *) node; |
957 | const FunctionNode *from = func->reimplementedFrom(); |
958 | if (from == 0) { |
959 | doc.location().warning( |
960 | tr("Cannot find base function for '\\%1' in %2()") |
961 | .arg(COMMAND_REIMP).arg(node->name()), |
962 | tr("The function either doesn't exist in any base class " |
963 | "with the same signature or it exists but isn't virtual.")); |
964 | } |
965 | /* |
966 | Ideally, we would enable this check to warn whenever |
967 | \reimp is used incorrectly, and only make the node |
968 | internal if the function is a reimplementation of |
969 | another function in a base class. |
970 | */ |
971 | else if (from->access() == Node::Private |
972 | || from->parent()->access() == Node::Private) { |
973 | doc.location().warning(tr("'\\%1' in %2() should be '\\internal' because its base function is private or internal") |
974 | .arg(COMMAND_REIMP).arg(node->name())); |
975 | } |
976 | |
977 | func->setReimp(true); |
978 | } |
979 | else { |
980 | doc.location().warning(tr("Ignored '\\%1' in %2") |
981 | .arg(COMMAND_REIMP) |
982 | .arg(node->name())); |
983 | } |
984 | } |
985 | else if (command == COMMAND_RELATES) { |
986 | InnerNode *pseudoParent; |
987 | if (arg.startsWith("<") || arg.startsWith( "\"")) { |
988 | pseudoParent = |
989 | static_cast<InnerNode *>(tre->findNode(QStringList(arg), |
990 | Node::Fake)); |
991 | } |
992 | else { |
993 | QStringList newPath = arg.split("::"); |
994 | pseudoParent = |
995 | static_cast<InnerNode*>(tre->findNode(QStringList(newPath), |
996 | Node::Class)); |
997 | if (!pseudoParent) |
998 | pseudoParent = |
999 | static_cast<InnerNode*>(tre->findNode(QStringList(newPath), |
1000 | Node::Namespace)); |
1001 | } |
1002 | if (!pseudoParent) { |
1003 | doc.location().warning(tr("Cannot find '%1' in '\\%2'") |
1004 | .arg(arg).arg(COMMAND_RELATES)); |
1005 | } |
1006 | else { |
1007 | node->setRelates(pseudoParent); |
1008 | } |
1009 | } |
1010 | else if (command == COMMAND_CONTENTSPAGE) { |
1011 | setLink(node, Node::ContentsLink, arg); |
1012 | } |
1013 | else if (command == COMMAND_NEXTPAGE) { |
1014 | setLink(node, Node::NextLink, arg); |
1015 | } |
1016 | else if (command == COMMAND_PREVIOUSPAGE) { |
1017 | setLink(node, Node::PreviousLink, arg); |
1018 | } |
1019 | else if (command == COMMAND_INDEXPAGE) { |
1020 | setLink(node, Node::IndexLink, arg); |
1021 | } |
1022 | else if (command == COMMAND_STARTPAGE) { |
1023 | setLink(node, Node::StartLink, arg); |
1024 | } |
1025 | #ifdef QDOC_QML |
1026 | else if (command == COMMAND_QMLINHERITS) { |
1027 | setLink(node, Node::InheritsLink, arg); |
1028 | if (node->subType() == Node::QmlClass) { |
1029 | QmlClassNode::addInheritedBy(arg,node); |
1030 | } |
1031 | } |
1032 | else if (command == COMMAND_QMLDEFAULT) { |
1033 | QmlPropGroupNode* qpgn = static_cast<QmlPropGroupNode*>(node); |
1034 | qpgn->setDefault(); |
1035 | } |
1036 | #endif |
1037 | else { |
1038 | processCommonMetaCommand(doc.location(),command,arg,node,tre); |
1039 | } |
1040 | } |
1041 | |
1042 | /*! |
1043 | The topic command has been processed resulting in the \a doc |
1044 | and \a node passed in here. Process the other meta commands, |
1045 | which are found in \a doc, in the context of the topic \a node. |
1046 | */ |
1047 | void CppCodeParser::processOtherMetaCommands(const Doc& doc, Node *node) |
1048 | { |
1049 | const QSet<QString> metaCommands = doc.metaCommandsUsed(); |
1050 | QSet<QString>::ConstIterator cmd = metaCommands.begin(); |
1051 | while (cmd != metaCommands.end()) { |
1052 | QStringList args = doc.metaCommandArgs(*cmd); |
1053 | QStringList::ConstIterator arg = args.begin(); |
1054 | while (arg != args.end()) { |
1055 | processOtherMetaCommand(doc, *cmd, *arg, node); |
1056 | ++arg; |
1057 | } |
1058 | ++cmd; |
1059 | } |
1060 | } |
1061 | |
1062 | /*! |
1063 | Resets the C++ code parser to its default initialized state. |
1064 | */ |
1065 | void CppCodeParser::reset(Tree *tree) |
1066 | { |
1067 | tre = tree; |
1068 | tokenizer = 0; |
1069 | tok = 0; |
1070 | access = Node::Public; |
1071 | metaness = FunctionNode::Plain; |
1072 | lastPath.clear(); |
1073 | moduleName = ""; |
1074 | } |
1075 | |
1076 | /*! |
1077 | Get the next token from the file being parsed and store it |
1078 | in the token variable. |
1079 | */ |
1080 | void CppCodeParser::readToken() |
1081 | { |
1082 | tok = tokenizer->getToken(); |
1083 | } |
1084 | |
1085 | /*! |
1086 | Return the current location in the file being parsed, |
1087 | i.e. the file name, line number, and column number. |
1088 | */ |
1089 | const Location& CppCodeParser::location() |
1090 | { |
1091 | return tokenizer->location(); |
1092 | } |
1093 | |
1094 | /*! |
1095 | Return the previous string read from the file being parsed. |
1096 | */ |
1097 | QString CppCodeParser::previousLexeme() |
1098 | { |
1099 | return tokenizer->previousLexeme(); |
1100 | } |
1101 | |
1102 | /*! |
1103 | Return the current string string from the file being parsed. |
1104 | */ |
1105 | QString CppCodeParser::lexeme() |
1106 | { |
1107 | return tokenizer->lexeme(); |
1108 | } |
1109 | |
1110 | bool CppCodeParser::match(int target) |
1111 | { |
1112 | if (tok == target) { |
1113 | readToken(); |
1114 | return true; |
1115 | } |
1116 | else |
1117 | return false; |
1118 | } |
1119 | |
1120 | /*! |
1121 | Skip to \a target. If \a target is found before the end |
1122 | of input, return true. Otherwise return false. |
1123 | */ |
1124 | bool CppCodeParser::skipTo(int target) |
1125 | { |
1126 | while ((tok != Tok_Eoi) && (tok != target)) |
1127 | readToken(); |
1128 | return (tok == target ? true : false); |
1129 | } |
1130 | |
1131 | /*! |
1132 | If the current token is one of the keyword thingees that |
1133 | are used in Qt, skip over it to the next token and return |
1134 | true. Otherwise just return false without reading the |
1135 | next token. |
1136 | */ |
1137 | bool CppCodeParser::matchCompat() |
1138 | { |
1139 | switch (tok) { |
1140 | case Tok_QT_COMPAT: |
1141 | case Tok_QT_COMPAT_CONSTRUCTOR: |
1142 | case Tok_QT_DEPRECATED: |
1143 | case Tok_QT_MOC_COMPAT: |
1144 | case Tok_QT3_SUPPORT: |
1145 | case Tok_QT3_SUPPORT_CONSTRUCTOR: |
1146 | case Tok_QT3_MOC_SUPPORT: |
1147 | readToken(); |
1148 | return true; |
1149 | default: |
1150 | return false; |
1151 | } |
1152 | } |
1153 | |
1154 | bool CppCodeParser::matchTemplateAngles(CodeChunk *dataType) |
1155 | { |
1156 | bool matches = (tok == Tok_LeftAngle); |
1157 | if (matches) { |
1158 | int leftAngleDepth = 0; |
1159 | int parenAndBraceDepth = 0; |
1160 | do { |
1161 | if (tok == Tok_LeftAngle) { |
1162 | leftAngleDepth++; |
1163 | } |
1164 | else if (tok == Tok_RightAngle) { |
1165 | leftAngleDepth--; |
1166 | } |
1167 | else if (tok == Tok_LeftParen || tok == Tok_LeftBrace) { |
1168 | ++parenAndBraceDepth; |
1169 | } |
1170 | else if (tok == Tok_RightParen || tok == Tok_RightBrace) { |
1171 | if (--parenAndBraceDepth < 0) |
1172 | return false; |
1173 | } |
1174 | |
1175 | if (dataType != 0) |
1176 | dataType->append(lexeme()); |
1177 | readToken(); |
1178 | } while (leftAngleDepth > 0 && tok != Tok_Eoi); |
1179 | } |
1180 | return matches; |
1181 | } |
1182 | |
1183 | bool CppCodeParser::matchTemplateHeader() |
1184 | { |
1185 | readToken(); |
1186 | return matchTemplateAngles(); |
1187 | } |
1188 | |
1189 | bool CppCodeParser::matchDataType(CodeChunk *dataType, QString *var) |
1190 | { |
1191 | /* |
1192 | This code is really hard to follow... sorry. The loop is there to match |
1193 | Alpha::Beta::Gamma::...::Omega. |
1194 | */ |
1195 | for (;;) { |
1196 | bool virgin = true; |
1197 | |
1198 | if (tok != Tok_Ident) { |
1199 | /* |
1200 | There is special processing for 'Foo::operator int()' |
1201 | and such elsewhere. This is the only case where we |
1202 | return something with a trailing gulbrandsen ('Foo::'). |
1203 | */ |
1204 | if (tok == Tok_operator) |
1205 | return true; |
1206 | |
1207 | /* |
1208 | People may write 'const unsigned short' or |
1209 | 'short unsigned const' or any other permutation. |
1210 | */ |
1211 | while (match(Tok_const) || match(Tok_volatile)) |
1212 | dataType->append(previousLexeme()); |
1213 | while (match(Tok_signed) || match(Tok_unsigned) || |
1214 | match(Tok_short) || match(Tok_long) || match(Tok_int64)) { |
1215 | dataType->append(previousLexeme()); |
1216 | virgin = false; |
1217 | } |
1218 | while (match(Tok_const) || match(Tok_volatile)) |
1219 | dataType->append(previousLexeme()); |
1220 | |
1221 | if (match(Tok_Tilde)) |
1222 | dataType->append(previousLexeme()); |
1223 | } |
1224 | |
1225 | if (virgin) { |
1226 | if (match(Tok_Ident)) |
1227 | dataType->append(previousLexeme()); |
1228 | else if (match(Tok_void) || match(Tok_int) || match(Tok_char) || |
1229 | match(Tok_double) || match(Tok_Ellipsis)) |
1230 | dataType->append(previousLexeme()); |
1231 | else |
1232 | return false; |
1233 | } |
1234 | else if (match(Tok_int) || match(Tok_char) || match(Tok_double)) { |
1235 | dataType->append(previousLexeme()); |
1236 | } |
1237 | |
1238 | matchTemplateAngles(dataType); |
1239 | |
1240 | while (match(Tok_const) || match(Tok_volatile)) |
1241 | dataType->append(previousLexeme()); |
1242 | |
1243 | if (match(Tok_Gulbrandsen)) |
1244 | dataType->append(previousLexeme()); |
1245 | else |
1246 | break; |
1247 | } |
1248 | |
1249 | while (match(Tok_Ampersand) || match(Tok_Aster) || match(Tok_const) || |
1250 | match(Tok_Caret)) |
1251 | dataType->append(previousLexeme()); |
1252 | |
1253 | if (match(Tok_LeftParenAster)) { |
1254 | /* |
1255 | A function pointer. This would be rather hard to handle without a |
1256 | tokenizer hack, because a type can be followed with a left parenthesis |
1257 | in some cases (e.g., 'operator int()'). The tokenizer recognizes '(*' |
1258 | as a single token. |
1259 | */ |
1260 | dataType->append(previousLexeme()); |
1261 | dataType->appendHotspot(); |
1262 | if (var != 0 && match(Tok_Ident)) |
1263 | *var = previousLexeme(); |
1264 | if (!match(Tok_RightParen) || tok != Tok_LeftParen) |
1265 | return false; |
1266 | dataType->append(previousLexeme()); |
1267 | |
1268 | int parenDepth0 = tokenizer->parenDepth(); |
1269 | while (tokenizer->parenDepth() >= parenDepth0 && tok != Tok_Eoi) { |
1270 | dataType->append(lexeme()); |
1271 | readToken(); |
1272 | } |
1273 | if (match(Tok_RightParen)) |
1274 | dataType->append(previousLexeme()); |
1275 | } |
1276 | else { |
1277 | /* |
1278 | The common case: Look for an optional identifier, then for |
1279 | some array brackets. |
1280 | */ |
1281 | dataType->appendHotspot(); |
1282 | |
1283 | if (var != 0) { |
1284 | if (match(Tok_Ident)) { |
1285 | *var = previousLexeme(); |
1286 | } |
1287 | else if (match(Tok_Comment)) { |
1288 | /* |
1289 | A neat hack: Commented-out parameter names are |
1290 | recognized by qdoc. It's impossible to illustrate |
1291 | here inside a C-style comment, because it requires |
1292 | an asterslash. It's also impossible to illustrate |
1293 | inside a C++-style comment, because the explanation |
1294 | does not fit on one line. |
1295 | */ |
1296 | if (varComment.exactMatch(previousLexeme())) |
1297 | *var = varComment.cap(1); |
1298 | } |
1299 | } |
1300 | |
1301 | if (tok == Tok_LeftBracket) { |
1302 | int bracketDepth0 = tokenizer->bracketDepth(); |
1303 | while ((tokenizer->bracketDepth() >= bracketDepth0 && |
1304 | tok != Tok_Eoi) || |
1305 | tok == Tok_RightBracket) { |
1306 | dataType->append(lexeme()); |
1307 | readToken(); |
1308 | } |
1309 | } |
1310 | } |
1311 | return true; |
1312 | } |
1313 | |
1314 | bool CppCodeParser::matchParameter(FunctionNode *func) |
1315 | { |
1316 | CodeChunk dataType; |
1317 | QString name; |
1318 | CodeChunk defaultValue; |
1319 | |
1320 | if (!matchDataType(&dataType, &name)) |
1321 | return false; |
1322 | match(Tok_Comment); |
1323 | if (match(Tok_Equal)) { |
1324 | int parenDepth0 = tokenizer->parenDepth(); |
1325 | |
1326 | while (tokenizer->parenDepth() >= parenDepth0 && |
1327 | (tok != Tok_Comma || |
1328 | tokenizer->parenDepth() > parenDepth0) && |
1329 | tok != Tok_Eoi) { |
1330 | defaultValue.append(lexeme()); |
1331 | readToken(); |
1332 | } |
1333 | } |
1334 | func->addParameter(Parameter(dataType.toString(), |
1335 | "", |
1336 | name, |
1337 | defaultValue.toString())); // ### |
1338 | return true; |
1339 | } |
1340 | |
1341 | bool CppCodeParser::matchFunctionDecl(InnerNode *parent, |
1342 | QStringList *parentPathPtr, |
1343 | FunctionNode **funcPtr, |
1344 | const QString &templateStuff, |
1345 | Node::Type type, |
1346 | bool attached) |
1347 | { |
1348 | CodeChunk returnType; |
1349 | QStringList parentPath; |
1350 | QString name; |
1351 | |
1352 | bool compat = false; |
1353 | |
1354 | if (match(Tok_friend)) |
1355 | return false; |
1356 | match(Tok_explicit); |
1357 | if (matchCompat()) |
1358 | compat = true; |
1359 | bool sta = false; |
1360 | if (match(Tok_static)) { |
1361 | sta = true; |
1362 | if (matchCompat()) |
1363 | compat = true; |
1364 | } |
1365 | FunctionNode::Virtualness vir = FunctionNode::NonVirtual; |
1366 | if (match(Tok_virtual)) { |
1367 | vir = FunctionNode::ImpureVirtual; |
1368 | if (matchCompat()) |
1369 | compat = true; |
1370 | } |
1371 | |
1372 | if (!matchDataType(&returnType)) { |
1373 | if (tokenizer->parsingFnOrMacro() |
1374 | && (match(Tok_Q_DECLARE_FLAGS) || |
1375 | match(Tok_Q_PROPERTY) || |
1376 | match(Tok_Q_PRIVATE_PROPERTY))) |
1377 | returnType = CodeChunk(previousLexeme()); |
1378 | else { |
1379 | return false; |
1380 | } |
1381 | } |
1382 | |
1383 | if (returnType.toString() == "QBool") |
1384 | returnType = CodeChunk("bool"); |
1385 | |
1386 | if (matchCompat()) |
1387 | compat = true; |
1388 | |
1389 | if (tok == Tok_operator && |
1390 | (returnType.toString().isEmpty() || |
1391 | returnType.toString().endsWith("::"))) { |
1392 | // 'QString::operator const char *()' |
1393 | parentPath = returnType.toString().split(sep); |
1394 | parentPath.removeAll(QString()); |
1395 | returnType = CodeChunk(); |
1396 | readToken(); |
1397 | |
1398 | CodeChunk restOfName; |
1399 | if (tok != Tok_Tilde && matchDataType(&restOfName)) { |
1400 | name = "operator "+ restOfName.toString(); |
1401 | } |
1402 | else { |
1403 | name = previousLexeme() + lexeme(); |
1404 | readToken(); |
1405 | while (tok != Tok_LeftParen && tok != Tok_Eoi) { |
1406 | name += lexeme(); |
1407 | readToken(); |
1408 | } |
1409 | } |
1410 | if (tok != Tok_LeftParen) { |
1411 | return false; |
1412 | } |
1413 | } |
1414 | else if (tok == Tok_LeftParen) { |
1415 | // constructor or destructor |
1416 | parentPath = returnType.toString().split(sep); |
1417 | if (!parentPath.isEmpty()) { |
1418 | name = parentPath.last(); |
1419 | parentPath.erase(parentPath.end() - 1); |
1420 | } |
1421 | returnType = CodeChunk(); |
1422 | } |
1423 | else { |
1424 | while (match(Tok_Ident)) { |
1425 | name = previousLexeme(); |
1426 | matchTemplateAngles(); |
1427 | |
1428 | if (match(Tok_Gulbrandsen)) |
1429 | parentPath.append(name); |
1430 | else |
1431 | break; |
1432 | } |
1433 | |
1434 | if (tok == Tok_operator) { |
1435 | name = lexeme(); |
1436 | readToken(); |
1437 | while (tok != Tok_Eoi) { |
1438 | name += lexeme(); |
1439 | readToken(); |
1440 | if (tok == Tok_LeftParen) |
1441 | break; |
1442 | } |
1443 | } |
1444 | if (parent && (tok == Tok_Semicolon || |
1445 | tok == Tok_LeftBracket || |
1446 | tok == Tok_Colon) |
1447 | && access != Node::Private) { |
1448 | if (tok == Tok_LeftBracket) { |
1449 | returnType.appendHotspot(); |
1450 | |
1451 | int bracketDepth0 = tokenizer->bracketDepth(); |
1452 | while ((tokenizer->bracketDepth() >= bracketDepth0 && |
1453 | tok != Tok_Eoi) || |
1454 | tok == Tok_RightBracket) { |
1455 | returnType.append(lexeme()); |
1456 | readToken(); |
1457 | } |
1458 | if (tok != Tok_Semicolon) { |
1459 | return false; |
1460 | } |
1461 | } |
1462 | else if (tok == Tok_Colon) { |
1463 | returnType.appendHotspot(); |
1464 | |
1465 | while (tok != Tok_Semicolon && tok != Tok_Eoi) { |
1466 | returnType.append(lexeme()); |
1467 | readToken(); |
1468 | } |
1469 | if (tok != Tok_Semicolon) { |
1470 | return false; |
1471 | } |
1472 | } |
1473 | |
1474 | VariableNode *var = new VariableNode(parent, name); |
1475 | var->setAccess(access); |
1476 | var->setLocation(location()); |
1477 | var->setLeftType(returnType.left()); |
1478 | var->setRightType(returnType.right()); |
1479 | if (compat) |
1480 | var->setStatus(Node::Compat); |
1481 | var->setStatic(sta); |
1482 | return false; |
1483 | } |
1484 | if (tok != Tok_LeftParen) { |
1485 | return false; |
1486 | } |
1487 | } |
1488 | readToken(); |
1489 | |
1490 | FunctionNode *func = new FunctionNode(type, parent, name, attached); |
1491 | func->setAccess(access); |
1492 | func->setLocation(location()); |
1493 | func->setReturnType(returnType.toString()); |
1494 | func->setParentPath(parentPath); |
1495 | func->setTemplateStuff(templateStuff); |
1496 | if (compat) |
1497 | func->setStatus(Node::Compat); |
1498 | |
1499 | func->setMetaness(metaness); |
1500 | if (parent) { |
1501 | if (name == parent->name()) { |
1502 | func->setMetaness(FunctionNode::Ctor); |
1503 | } else if (name.startsWith("~")) { |
1504 | func->setMetaness(FunctionNode::Dtor); |
1505 | } |
1506 | } |
1507 | func->setStatic(sta); |
1508 | |
1509 | if (tok != Tok_RightParen) { |
1510 | do { |
1511 | if (!matchParameter(func)) { |
1512 | return false; |
1513 | } |
1514 | } while (match(Tok_Comma)); |
1515 | } |
1516 | if (!match(Tok_RightParen)) { |
1517 | return false; |
1518 | } |
1519 | |
1520 | func->setConst(match(Tok_const)); |
1521 | |
1522 | if (match(Tok_Equal) && match(Tok_Number)) |
1523 | vir = FunctionNode::PureVirtual; |
1524 | func->setVirtualness(vir); |
1525 | |
1526 | if (match(Tok_Colon)) { |
1527 | while (tok != Tok_LeftBrace && tok != Tok_Eoi) |
1528 | readToken(); |
1529 | } |
1530 | |
1531 | if (!match(Tok_Semicolon) && tok != Tok_Eoi) { |
1532 | int braceDepth0 = tokenizer->braceDepth(); |
1533 | |
1534 | if (!match(Tok_LeftBrace)) { |
1535 | return false; |
1536 | } |
1537 | while (tokenizer->braceDepth() >= braceDepth0 && tok != Tok_Eoi) |
1538 | readToken(); |
1539 | match(Tok_RightBrace); |
1540 | } |
1541 | if (parentPathPtr != 0) |
1542 | *parentPathPtr = parentPath; |
1543 | if (funcPtr != 0) |
1544 | *funcPtr = func; |
1545 | return true; |
1546 | } |
1547 | |
1548 | bool CppCodeParser::matchBaseSpecifier(ClassNode *classe, bool isClass) |
1549 | { |
1550 | Node::Access access; |
1551 | |
1552 | switch (tok) { |
1553 | case Tok_public: |
1554 | access = Node::Public; |
1555 | readToken(); |
1556 | break; |
1557 | case Tok_protected: |
1558 | access = Node::Protected; |
1559 | readToken(); |
1560 | break; |
1561 | case Tok_private: |
1562 | access = Node::Private; |
1563 | readToken(); |
1564 | break; |
1565 | default: |
1566 | access = isClass ? Node::Private : Node::Public; |
1567 | } |
1568 | |
1569 | if (tok == Tok_virtual) |
1570 | readToken(); |
1571 | |
1572 | CodeChunk baseClass; |
1573 | if (!matchDataType(&baseClass)) |
1574 | return false; |
1575 | |
1576 | tre->addBaseClass(classe, |
1577 | access, |
1578 | baseClass.toPath(), |
1579 | baseClass.toString(), |
1580 | classe->parent()); |
1581 | return true; |
1582 | } |
1583 | |
1584 | bool CppCodeParser::matchBaseList(ClassNode *classe, bool isClass) |
1585 | { |
1586 | for (;;) { |
1587 | if (!matchBaseSpecifier(classe, isClass)) |
1588 | return false; |
1589 | if (tok == Tok_LeftBrace) |
1590 | return true; |
1591 | if (!match(Tok_Comma)) |
1592 | return false; |
1593 | } |
1594 | } |
1595 | |
1596 | /*! |
1597 | Parse a C++ class, union, or struct declarion. |
1598 | */ |
1599 | bool CppCodeParser::matchClassDecl(InnerNode *parent, |
1600 | const QString &templateStuff) |
1601 | { |
1602 | bool isClass = (tok == Tok_class); |
1603 | readToken(); |
1604 | |
1605 | bool compat = matchCompat(); |
1606 | |
1607 | if (tok != Tok_Ident) |
1608 | return false; |
1609 | while (tok == Tok_Ident) |
1610 | readToken(); |
1611 | if (tok != Tok_Colon && tok != Tok_LeftBrace) |
1612 | return false; |
1613 | |
1614 | /* |
1615 | So far, so good. We have 'class Foo {' or 'class Foo :'. |
1616 | This is enough to recognize a class definition. |
1617 | */ |
1618 | ClassNode *classe = new ClassNode(parent, previousLexeme()); |
1619 | classe->setAccess(access); |
1620 | classe->setLocation(location()); |
1621 | if (compat) |
1622 | classe->setStatus(Node::Compat); |
1623 | if (!moduleName.isEmpty()) |
1624 | classe->setModuleName(moduleName); |
1625 | classe->setTemplateStuff(templateStuff); |
1626 | |
1627 | if (match(Tok_Colon) && !matchBaseList(classe, isClass)) |
1628 | return false; |
1629 | if (!match(Tok_LeftBrace)) |
1630 | return false; |
1631 | |
1632 | Node::Access outerAccess = access; |
1633 | access = isClass ? Node::Private : Node::Public; |
1634 | FunctionNode::Metaness outerMetaness = metaness; |
1635 | metaness = FunctionNode::Plain; |
1636 | |
1637 | bool matches = (matchDeclList(classe) && match(Tok_RightBrace) && |
1638 | match(Tok_Semicolon)); |
1639 | access = outerAccess; |
1640 | metaness = outerMetaness; |
1641 | return matches; |
1642 | } |
1643 | |
1644 | bool CppCodeParser::matchNamespaceDecl(InnerNode *parent) |
1645 | { |
1646 | readToken(); // skip 'namespace' |
1647 | if (tok != Tok_Ident) |
1648 | return false; |
1649 | while (tok == Tok_Ident) |
1650 | readToken(); |
1651 | if (tok != Tok_LeftBrace) |
1652 | return false; |
1653 | |
1654 | /* |
1655 | So far, so good. We have 'namespace Foo {'. |
1656 | */ |
1657 | QString namespaceName = previousLexeme(); |
1658 | NamespaceNode *namespasse = 0; |
1659 | if (parent) { |
1660 | namespasse = static_cast<NamespaceNode*>(parent->findNode(namespaceName, Node::Namespace)); |
1661 | } |
1662 | if (!namespasse) { |
1663 | namespasse = new NamespaceNode(parent, namespaceName); |
1664 | namespasse->setAccess(access); |
1665 | namespasse->setLocation(location()); |
1666 | } |
1667 | |
1668 | readToken(); // skip '{' |
1669 | bool matched = matchDeclList(namespasse); |
1670 | |
1671 | return matched && match(Tok_RightBrace); |
1672 | } |
1673 | |
1674 | bool CppCodeParser::matchUsingDecl() |
1675 | { |
1676 | readToken(); // skip 'using' |
1677 | |
1678 | // 'namespace' |
1679 | if (tok != Tok_namespace) |
1680 | return false; |
1681 | |
1682 | readToken(); |
1683 | // identifier |
1684 | if (tok != Tok_Ident) |
1685 | return false; |
1686 | |
1687 | QString name; |
1688 | while (tok == Tok_Ident) { |
1689 | name += lexeme(); |
1690 | readToken(); |
1691 | if (tok == Tok_Semicolon) |
1692 | break; |
1693 | else if (tok != Tok_Gulbrandsen) |
1694 | return false; |
1695 | name += "::"; |
1696 | readToken(); |
1697 | } |
1698 | |
1699 | /* |
1700 | So far, so good. We have 'using namespace Foo;'. |
1701 | */ |
1702 | usedNamespaces.insert(name); |
1703 | return true; |
1704 | } |
1705 | |
1706 | bool CppCodeParser::matchEnumItem(InnerNode *parent, EnumNode *enume) |
1707 | { |
1708 | if (!match(Tok_Ident)) |
1709 | return false; |
1710 | |
1711 | QString name = previousLexeme(); |
1712 | CodeChunk val; |
1713 | |
1714 | if (match(Tok_Equal)) { |
1715 | while (tok != Tok_Comma && tok != Tok_RightBrace && |
1716 | tok != Tok_Eoi) { |
1717 | val.append(lexeme()); |
1718 | readToken(); |
1719 | } |
1720 | } |
1721 | |
1722 | if (enume) { |
1723 | QString strVal = val.toString(); |
1724 | if (strVal.isEmpty()) { |
1725 | if (enume->items().isEmpty()) { |
1726 | strVal = "0"; |
1727 | } |
1728 | else { |
1729 | QString last = enume->items().last().value(); |
1730 | bool ok; |
1731 | int n = last.toInt(&ok); |
1732 | if (ok) { |
1733 | if (last.startsWith("0") && last.size() > 1) { |
1734 | if (last.startsWith("0x") || last.startsWith( "0X")) |
1735 | strVal = last.left(2) + QString::number(n + 1, 16); |
1736 | else |
1737 | strVal = "0"+ QString::number(n + 1, 8); |
1738 | } |
1739 | else |
1740 | strVal = QString::number(n + 1); |
1741 | } |
1742 | } |
1743 | } |
1744 | |
1745 | enume->addItem(EnumItem(name, strVal)); |
1746 | } |
1747 | else { |
1748 | VariableNode *var = new VariableNode(parent, name); |
1749 | var->setAccess(access); |
1750 | var->setLocation(location()); |
1751 | var->setLeftType("const int"); |
1752 | var->setStatic(true); |
1753 | } |
1754 | return true; |
1755 | } |
1756 | |
1757 | bool CppCodeParser::matchEnumDecl(InnerNode *parent) |
1758 | { |
1759 | QString name; |
1760 | |
1761 | if (!match(Tok_enum)) |
1762 | return false; |
1763 | if (match(Tok_Ident)) |
1764 | name = previousLexeme(); |
1765 | if (tok != Tok_LeftBrace) |
1766 | return false; |
1767 | |
1768 | EnumNode *enume = 0; |
1769 | |
1770 | if (!name.isEmpty()) { |
1771 | enume = new EnumNode(parent, name); |
1772 | enume->setAccess(access); |
1773 | enume->setLocation(location()); |
1774 | } |
1775 | |
1776 | readToken(); |
1777 | |
1778 | if (!matchEnumItem(parent, enume)) |
1779 | return false; |
1780 | |
1781 | while (match(Tok_Comma)) { |
1782 | if (!matchEnumItem(parent, enume)) |
1783 | return false; |
1784 | } |
1785 | return match(Tok_RightBrace) && match(Tok_Semicolon); |
1786 | } |
1787 | |
1788 | bool CppCodeParser::matchTypedefDecl(InnerNode *parent) |
1789 | { |
1790 | CodeChunk dataType; |
1791 | QString name; |
1792 | |
1793 | if (!match(Tok_typedef)) |
1794 | return false; |
1795 | if (!matchDataType(&dataType, &name)) |
1796 | return false; |
1797 | if (!match(Tok_Semicolon)) |
1798 | return false; |
1799 | |
1800 | if (parent && !parent->findNode(name, Node::Typedef)) { |
1801 | TypedefNode *typedeffe = new TypedefNode(parent, name); |
1802 | typedeffe->setAccess(access); |
1803 | typedeffe->setLocation(location()); |
1804 | } |
1805 | return true; |
1806 | } |
1807 | |
1808 | bool CppCodeParser::matchProperty(InnerNode *parent) |
1809 | { |
1810 | int expected_tok = Tok_LeftParen; |
1811 | if (match(Tok_Q_PRIVATE_PROPERTY)) { |
1812 | expected_tok = Tok_Comma; |
1813 | if (!skipTo(Tok_Comma)) |
1814 | return false; |
1815 | } |
1816 | else if (!match(Tok_Q_PROPERTY) && |
1817 | !match(Tok_Q_OVERRIDE) && |
1818 | !match(Tok_QDOC_PROPERTY)) { |
1819 | return false; |
1820 | } |
1821 | |
1822 | if (!match(expected_tok)) |
1823 | return false; |
1824 | |
1825 | QString name; |
1826 | CodeChunk dataType; |
1827 | if (!matchDataType(&dataType, &name)) |
1828 | return false; |
1829 | |
1830 | PropertyNode *property = new PropertyNode(parent, name); |
1831 | property->setAccess(Node::Public); |
1832 | property->setLocation(location()); |
1833 | property->setDataType(dataType.toString()); |
1834 | |
1835 | while (tok != Tok_RightParen && tok != Tok_Eoi) { |
1836 | if (!match(Tok_Ident)) |
1837 | return false; |
1838 | QString key = previousLexeme(); |
1839 | QString value; |
1840 | |
1841 | if (match(Tok_Ident) || match(Tok_Number)) { |
1842 | value = previousLexeme(); |
1843 | } |
1844 | else if (match(Tok_LeftParen)) { |
1845 | int depth = 1; |
1846 | while (tok != Tok_Eoi) { |
1847 | if (tok == Tok_LeftParen) { |
1848 | readToken(); |
1849 | ++depth; |
1850 | } else if (tok == Tok_RightParen) { |
1851 | readToken(); |
1852 | if (--depth == 0) |
1853 | break; |
1854 | } else { |
1855 | readToken(); |
1856 | } |
1857 | } |
1858 | value = "?"; |
1859 | } |
1860 | |
1861 | if (key == "READ") |
1862 | tre->addPropertyFunction(property, value, PropertyNode::Getter); |
1863 | else if (key == "WRITE") { |
1864 | tre->addPropertyFunction(property, value, PropertyNode::Setter); |
1865 | property->setWritable(true); |
1866 | } |
1867 | else if (key == "STORED") |
1868 | property->setStored(value.toLower() == "true"); |
1869 | else if (key == "DESIGNABLE") { |
1870 | QString v = value.toLower(); |
1871 | if (v == "true") |
1872 | property->setDesignable(true); |
1873 | else if (v == "false") |
1874 | property->setDesignable(false); |
1875 | else { |
1876 | property->setDesignable(false); |
1877 | property->setRuntimeDesFunc(value); |
1878 | } |
1879 | } |
1880 | else if (key == "RESET") |
1881 | tre->addPropertyFunction(property, value, PropertyNode::Resetter); |
1882 | else if (key == "NOTIFY") { |
1883 | tre->addPropertyFunction(property, value, PropertyNode::Notifier); |
1884 | } else if (key == "REVISION") { |
1885 | int revision; |
1886 | bool ok; |
1887 | revision = value.toInt(&ok); |
1888 | if (ok) |
1889 | property->setRevision(revision); |
1890 | else |
1891 | parent->doc().location().warning(tr("Invalid revision number: %1").arg(value)); |
1892 | } else if (key == "SCRIPTABLE") { |
1893 | QString v = value.toLower(); |
1894 | if (v == "true") |
1895 | property->setScriptable(true); |
1896 | else if (v == "false") |
1897 | property->setScriptable(false); |
1898 | else { |
1899 | property->setScriptable(false); |
1900 | property->setRuntimeScrFunc(value); |
1901 | } |
1902 | } |
1903 | else if (key == "CONSTANT") |
1904 | property->setConstant(); |
1905 | else if (key == "FINAL") |
1906 | property->setFinal(); |
1907 | } |
1908 | match(Tok_RightParen); |
1909 | return true; |
1910 | } |
1911 | |
1912 | /*! |
1913 | Parse a C++ declaration. |
1914 | */ |
1915 | bool CppCodeParser::matchDeclList(InnerNode *parent) |
1916 | { |
1917 | QString templateStuff; |
1918 | int braceDepth0 = tokenizer->braceDepth(); |
1919 | if (tok == Tok_RightBrace) // prevents failure on empty body |
1920 | braceDepth0++; |
1921 | |
1922 | while (tokenizer->braceDepth() >= braceDepth0 && tok != Tok_Eoi) { |
1923 | switch (tok) { |
1924 | case Tok_Colon: |
1925 | readToken(); |
1926 | break; |
1927 | case Tok_class: |
1928 | case Tok_struct: |
1929 | case Tok_union: |
1930 | matchClassDecl(parent, templateStuff); |
1931 | break; |
1932 | case Tok_namespace: |
1933 | matchNamespaceDecl(parent); |
1934 | break; |
1935 | case Tok_using: |
1936 | matchUsingDecl(); |
1937 | break; |
1938 | case Tok_template: |
1939 | templateStuff = matchTemplateHeader(); |
1940 | continue; |
1941 | case Tok_enum: |
1942 | matchEnumDecl(parent); |
1943 | break; |
1944 | case Tok_typedef: |
1945 | matchTypedefDecl(parent); |
1946 | break; |
1947 | case Tok_private: |
1948 | readToken(); |
1949 | access = Node::Private; |
1950 | metaness = FunctionNode::Plain; |
1951 | break; |
1952 | case Tok_protected: |
1953 | readToken(); |
1954 | access = Node::Protected; |
1955 | metaness = FunctionNode::Plain; |
1956 | break; |
1957 | case Tok_public: |
1958 | readToken(); |
1959 | access = Node::Public; |
1960 | metaness = FunctionNode::Plain; |
1961 | break; |
1962 | case Tok_signals: |
1963 | case Tok_Q_SIGNALS: |
1964 | readToken(); |
1965 | access = Node::Public; |
1966 | metaness = FunctionNode::Signal; |
1967 | break; |
1968 | case Tok_slots: |
1969 | case Tok_Q_SLOTS: |
1970 | readToken(); |
1971 | metaness = FunctionNode::Slot; |
1972 | break; |
1973 | case Tok_Q_OBJECT: |
1974 | readToken(); |
1975 | break; |
1976 | case Tok_Q_OVERRIDE: |
1977 | case Tok_Q_PROPERTY: |
1978 | case Tok_Q_PRIVATE_PROPERTY: |
1979 | case Tok_QDOC_PROPERTY: |
1980 | matchProperty(parent); |
1981 | break; |
1982 | case Tok_Q_DECLARE_SEQUENTIAL_ITERATOR: |
1983 | readToken(); |
1984 | if (match(Tok_LeftParen) && match(Tok_Ident)) |
1985 | sequentialIteratorClasses.insert(previousLexeme(), |
1986 | location().fileName()); |
1987 | match(Tok_RightParen); |
1988 | break; |
1989 | case Tok_Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR: |
1990 | readToken(); |
1991 | if (match(Tok_LeftParen) && match(Tok_Ident)) |
1992 | mutableSequentialIteratorClasses.insert(previousLexeme(), |
1993 | location().fileName()); |
1994 | match(Tok_RightParen); |
1995 | break; |
1996 | case Tok_Q_DECLARE_ASSOCIATIVE_ITERATOR: |
1997 | readToken(); |
1998 | if (match(Tok_LeftParen) && match(Tok_Ident)) |
1999 | associativeIteratorClasses.insert(previousLexeme(), |
2000 | location().fileName()); |
2001 | match(Tok_RightParen); |
2002 | break; |
2003 | case Tok_Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR: |
2004 | readToken(); |
2005 | if (match(Tok_LeftParen) && match(Tok_Ident)) |
2006 | mutableAssociativeIteratorClasses.insert(previousLexeme(), |
2007 | location().fileName()); |
2008 | match(Tok_RightParen); |
2009 | break; |
2010 | case Tok_Q_DECLARE_FLAGS: |
2011 | readToken(); |
2012 | if (match(Tok_LeftParen) && match(Tok_Ident)) { |
2013 | QString flagsType = previousLexeme(); |
2014 | if (match(Tok_Comma) && match(Tok_Ident)) { |
2015 | QString enumType = previousLexeme(); |
2016 | TypedefNode *flagsNode = new TypedefNode(parent, flagsType); |
2017 | flagsNode->setAccess(access); |
2018 | flagsNode->setLocation(location()); |
2019 | EnumNode *enumNode = |
2020 | static_cast<EnumNode*>(parent->findNode(enumType, |
2021 | Node::Enum)); |
2022 | if (enumNode) |
2023 | enumNode->setFlagsType(flagsNode); |
2024 | } |
2025 | } |
2026 | match(Tok_RightParen); |
2027 | break; |
2028 | case Tok_QT_MODULE: |
2029 | readToken(); |
2030 | if (match(Tok_LeftParen) && match(Tok_Ident)) |
2031 | moduleName = previousLexeme(); |
2032 | if (!moduleName.startsWith("Qt")) |
2033 | moduleName.prepend("Qt"); |
2034 | match(Tok_RightParen); |
2035 | break; |
2036 | default: |
2037 | if (!matchFunctionDecl(parent, 0, 0, templateStuff)) { |
2038 | while (tok != Tok_Eoi && |
2039 | (tokenizer->braceDepth() > braceDepth0 || |
2040 | (!match(Tok_Semicolon) && |
2041 | tok != Tok_public && tok != Tok_protected && |
2042 | tok != Tok_private))) |
2043 | readToken(); |
2044 | } |
2045 | } |
2046 | templateStuff.clear(); |
2047 | } |
2048 | return true; |
2049 | } |
2050 | |
2051 | /*! |
2052 | This is called by parseSourceFile() to do the actual parsing |
2053 | and tree building. |
2054 | */ |
2055 | bool CppCodeParser::matchDocsAndStuff() |
2056 | { |
2057 | QSet<QString> topicCommandsAllowed = topicCommands(); |
2058 | QSet<QString> otherMetacommandsAllowed = otherMetaCommands(); |
2059 | QSet<QString> metacommandsAllowed = topicCommandsAllowed + |
2060 | otherMetacommandsAllowed; |
2061 | |
2062 | while (tok != Tok_Eoi) { |
2063 | if (tok == Tok_Doc) { |
2064 | /* |
2065 | lexeme() returns an entire qdoc comment. |
2066 | */ |
2067 | QString comment = lexeme(); |
2068 | Location start_loc(location()); |
2069 | readToken(); |
2070 | |
2071 | Doc::trimCStyleComment(start_loc,comment); |
2072 | Location end_loc(location()); |
2073 | |
2074 | /* |
2075 | Doc parses the comment. |
2076 | */ |
2077 | Doc doc(start_loc,end_loc,comment,metacommandsAllowed); |
2078 | |
2079 | QString topic; |
2080 | QStringList args; |
2081 | |
2082 | QSet<QString> topicCommandsUsed = topicCommandsAllowed & |
2083 | doc.metaCommandsUsed(); |
2084 | |
2085 | /* |
2086 | There should be one topic command in the set, |
2087 | or none. If the set is empty, then the comment |
2088 | should be a function description. |
2089 | */ |
2090 | if (topicCommandsUsed.count() > 0) { |
2091 | topic = *topicCommandsUsed.begin(); |
2092 | args = doc.metaCommandArgs(topic); |
2093 | } |
2094 | |
2095 | NodeList nodes; |
2096 | QList<Doc> docs; |
2097 | |
2098 | if (topic.isEmpty()) { |
2099 | QStringList parentPath; |
2100 | FunctionNode *clone; |
2101 | FunctionNode *func = 0; |
2102 | |
2103 | if (matchFunctionDecl(0, &parentPath, &clone)) { |
2104 | foreach (const QString &usedNamespace, usedNamespaces) { |
2105 | QStringList newPath = usedNamespace.split("::") + parentPath; |
2106 | func = tre->findFunctionNode(newPath, clone); |
2107 | if (func) |
2108 | break; |
2109 | } |
2110 | if (func == 0) |
2111 | func = tre->findFunctionNode(parentPath, clone); |
2112 | |
2113 | if (func) { |
2114 | func->borrowParameterNames(clone); |
2115 | nodes.append(func); |
2116 | docs.append(doc); |
2117 | } |
2118 | delete clone; |
2119 | } |
2120 | else { |
2121 | doc.location().warning( |
2122 | tr("Cannot tie this documentation to anything"), |
2123 | tr("I found a /*! ... */ comment, but there was no " |
2124 | "topic command (e.g., '\\%1', '\\%2') in the " |
2125 | "comment and no function definition following " |
2126 | "the comment.") |
2127 | .arg(COMMAND_FN).arg(COMMAND_PAGE)); |
2128 | } |
2129 | } |
2130 | else { |
2131 | /* |
2132 | There is a topic command. Process it. |
2133 | */ |
2134 | #ifdef QDOC_QML |
2135 | if ((topic == COMMAND_QMLPROPERTY) || |
2136 | (topic == COMMAND_QMLATTACHEDPROPERTY)) { |
2137 | Doc nodeDoc = doc; |
2138 | Node *node = processTopicCommandGroup(nodeDoc,topic,args); |
2139 | if (node != 0) { |
2140 | nodes.append(node); |
2141 | docs.append(nodeDoc); |
2142 | } |
2143 | } |
2144 | else { |
2145 | QStringList::ConstIterator a = args.begin(); |
2146 | while (a != args.end()) { |
2147 | Doc nodeDoc = doc; |
2148 | Node *node = processTopicCommand(nodeDoc,topic,*a); |
2149 | if (node != 0) { |
2150 | nodes.append(node); |
2151 | docs.append(nodeDoc); |
2152 | } |
2153 | ++a; |
2154 | } |
2155 | } |
2156 | #else |
2157 | QStringList::ConstIterator a = args.begin(); |
2158 | while (a != args.end()) { |
2159 | Doc nodeDoc = doc; |
2160 | Node *node = processTopicCommand(nodeDoc, topic, *a); |
2161 | if (node != 0) { |
2162 | nodes.append(node); |
2163 | docs.append(nodeDoc); |
2164 | } |
2165 | ++a; |
2166 | } |
2167 | #endif |
2168 | } |
2169 | |
2170 | NodeList::Iterator n = nodes.begin(); |
2171 | QList<Doc>::Iterator d = docs.begin(); |
2172 | while (n != nodes.end()) { |
2173 | processOtherMetaCommands(*d, *n); |
2174 | (*n)->setDoc(*d); |
2175 | if ((*n)->isInnerNode() && |
2176 | ((InnerNode *)*n)->includes().isEmpty()) { |
2177 | InnerNode *m = static_cast<InnerNode *>(*n); |
2178 | while (m->parent() != tre->root()) |
2179 | m = m->parent(); |
2180 | if (m == *n) |
2181 | ((InnerNode *)*n)->addInclude((*n)->name()); |
2182 | else |
2183 | ((InnerNode *)*n)->setIncludes(m->includes()); |
2184 | } |
2185 | ++d; |
2186 | ++n; |
2187 | } |
2188 | } |
2189 | else if (tok == Tok_using) { |
2190 | matchUsingDecl(); |
2191 | } |
2192 | else { |
2193 | QStringList parentPath; |
2194 | FunctionNode *clone; |
2195 | FunctionNode *node = 0; |
2196 | |
2197 | if (matchFunctionDecl(0, &parentPath, &clone)) { |
2198 | /* |
2199 | The location of the definition is more interesting |
2200 | than that of the declaration. People equipped with |
2201 | a sophisticated text editor can respond to warnings |
2202 | concerning undocumented functions very quickly. |
2203 | |
2204 | Signals are implemented in uninteresting files |
2205 | generated by moc. |
2206 | */ |
2207 | node = tre->findFunctionNode(parentPath, clone); |
2208 | if (node != 0 && node->metaness() != FunctionNode::Signal) |
2209 | node->setLocation(clone->location()); |
2210 | delete clone; |
2211 | } |
2212 | else { |
2213 | if (tok != Tok_Doc) |
2214 | readToken(); |
2215 | } |
2216 | } |
2217 | } |
2218 | return true; |
2219 | } |
2220 | |
2221 | bool CppCodeParser::makeFunctionNode(const QString& synopsis, |
2222 | QStringList *parentPathPtr, |
2223 | FunctionNode **funcPtr, |
2224 | InnerNode *root, |
2225 | Node::Type type, |
2226 | bool attached) |
2227 | { |
2228 | Tokenizer *outerTokenizer = tokenizer; |
2229 | int outerTok = tok; |
2230 | |
2231 | Location loc; |
2232 | QByteArray latin1 = synopsis.toLatin1(); |
2233 | Tokenizer stringTokenizer(loc, latin1); |
2234 | stringTokenizer.setParsingFnOrMacro(true); |
2235 | tokenizer = &stringTokenizer; |
2236 | readToken(); |
2237 | |
2238 | bool ok = matchFunctionDecl(root, parentPathPtr, funcPtr, QString(), type, attached); |
2239 | // potential memory leak with funcPtr |
2240 | |
2241 | tokenizer = outerTokenizer; |
2242 | tok = outerTok; |
2243 | return ok; |
2244 | } |
2245 | |
2246 | /*! |
2247 | Create a new FunctionNode for a QML method or signal, as |
2248 | specified by \a type, as a child of \a parent. \a sig is |
2249 | the complete signature, and if \a attached is true, the |
2250 | method or signal is "attached". \a qdoctag is the text of |
2251 | the \a type. |
2252 | */ |
2253 | FunctionNode* CppCodeParser::makeFunctionNode(const Doc& doc, |
2254 | const QString& sig, |
2255 | InnerNode* parent, |
2256 | Node::Type type, |
2257 | bool attached, |
2258 | QString qdoctag) |
2259 | { |
2260 | QStringList pp; |
2261 | FunctionNode* fn = 0; |
2262 | if (!makeFunctionNode(sig,&pp,&fn,parent,type,attached) && |
2263 | !makeFunctionNode("void "+sig,&pp,&fn,parent,type,attached)) { |
2264 | doc.location().warning(tr("Invalid syntax in '\\%1'").arg(qdoctag)); |
2265 | } |
2266 | if (fn) |
2267 | return fn; |
2268 | return 0; |
2269 | } |
2270 | |
2271 | void CppCodeParser::parseQiteratorDotH(const Location &location, |
2272 | const QString &filePath) |
2273 | { |
2274 | QFile file(filePath); |
2275 | if (!file.open(QFile::ReadOnly)) |
2276 | return; |
2277 | |
2278 | QString text = file.readAll(); |
2279 | text.remove("\r"); |
2280 | text.replace("\\\n", ""); |
2281 | QStringList lines = text.split("\n"); |
2282 | lines = lines.filter("Q_DECLARE"); |
2283 | lines.replaceInStrings(QRegExp("#define Q[A-Z_]*\\(C\\)"), ""); |
2284 | |
2285 | if (lines.size() == 4) { |
2286 | sequentialIteratorDefinition = lines[0]; |
2287 | mutableSequentialIteratorDefinition = lines[1]; |
2288 | associativeIteratorDefinition = lines[2]; |
2289 | mutableAssociativeIteratorDefinition = lines[3]; |
2290 | } |
2291 | else { |
2292 | location.warning(tr("The qiterator.h hack failed")); |
2293 | } |
2294 | } |
2295 | |
2296 | void CppCodeParser::instantiateIteratorMacro(const QString &container, |
2297 | const QString &includeFile, |
2298 | const QString ¯oDef, |
2299 | Tree * /* tree */) |
2300 | { |
2301 | QString resultingCode = macroDef; |
2302 | resultingCode.replace(QRegExp("\\bC\\b"), container); |
2303 | resultingCode.replace(QRegExp("\\s*##\\s*"), ""); |
2304 | |
2305 | Location loc(includeFile); // hack to get the include file for free |
2306 | QByteArray latin1 = resultingCode.toLatin1(); |
2307 | Tokenizer stringTokenizer(loc, latin1); |
2308 | tokenizer = &stringTokenizer; |
2309 | readToken(); |
2310 | matchDeclList(tre->root()); |
2311 | } |
2312 | |
2313 | void CppCodeParser::createExampleFileNodes(FakeNode *fake) |
2314 | { |
2315 | QString examplePath = fake->name(); |
2316 | QString proFileName = examplePath + "/"+ examplePath.split( "/").last() + ".pro"; |
2317 | QString userFriendlyFilePath; |
2318 | |
2319 | QString fullPath = Config::findFile(fake->doc().location(), |
2320 | exampleFiles, |
2321 | exampleDirs, |
2322 | proFileName, |
2323 | userFriendlyFilePath); |
2324 | |
2325 | if (fullPath.isEmpty()) { |
2326 | QString tmp = proFileName; |
2327 | proFileName = examplePath + "/"+ "qbuild.pro"; |
2328 | userFriendlyFilePath.clear(); |
2329 | fullPath = Config::findFile(fake->doc().location(), |
2330 | exampleFiles, |
2331 | exampleDirs, |
2332 | proFileName, |
2333 | userFriendlyFilePath); |
2334 | if (fullPath.isEmpty()) { |
2335 | proFileName = examplePath + "/"+ examplePath.split( "/").last() + ".qmlproject"; |
2336 | userFriendlyFilePath.clear(); |
2337 | fullPath = Config::findFile(fake->doc().location(), |
2338 | exampleFiles, |
2339 | exampleDirs, |
2340 | proFileName, |
2341 | userFriendlyFilePath); |
2342 | if (fullPath.isEmpty()) { |
2343 | fake->doc().location().warning(tr("Cannot find file '%1' or '%2'").arg(tmp).arg(proFileName)); |
2344 | fake->doc().location().warning(tr("EXAMPLE PATH DOES NOT EXIST: %1").arg(examplePath)); |
2345 | return; |
2346 | } |
2347 | } |
2348 | } |
2349 | |
2350 | int sizeOfBoringPartOfName = fullPath.size() - proFileName.size(); |
2351 | fullPath.truncate(fullPath.lastIndexOf( |
2352 | |
2353 | QStringList exampleFiles = Config::getFilesHere(fullPath,exampleNameFilter); |
2354 | QString imagesPath = fullPath + "/images"; |
2355 | QStringList imageFiles = Config::getFilesHere(imagesPath,exampleImageFilter); |
2356 | if (!exampleFiles.isEmpty()) { |
2357 | // move main.cpp and to the end, if it exists |
2358 | QString mainCpp; |
2359 | QMutableStringListIterator i(exampleFiles); |
2360 | i.toBack(); |
2361 | while (i.hasPrevious()) { |
2362 | QString fileName = i.previous(); |
2363 | if (fileName.endsWith("/main.cpp")) { |
2364 | mainCpp = fileName; |
2365 | i.remove(); |
2366 | } |
2367 | else if (fileName.contains("/qrc_") || fileName.contains( "/moc_") |
2368 | || fileName.contains("/ui_")) |
2369 | i.remove(); |
2370 | } |
2371 | if (!mainCpp.isEmpty()) |
2372 | exampleFiles.append(mainCpp); |
2373 | |
2374 | // add any qmake Qt resource files and qmake project files |
2375 | exampleFiles += Config::getFilesHere(fullPath, "*.qrc *.pro *.qmlproject qmldir"); |
2376 | } |
2377 | |
2378 | foreach (const QString &exampleFile, exampleFiles) |
2379 | (void) new FakeNode(fake, |
2380 | exampleFile.mid(sizeOfBoringPartOfName), |
2381 | Node::File); |
2382 | foreach (const QString &imageFile, imageFiles) { |
2383 | new FakeNode(fake, |
2384 | imageFile.mid(sizeOfBoringPartOfName), |
2385 | Node::Image); |
2386 | } |
2387 | } |
2388 | |
2389 | QT_END_NAMESPACE |
2390 |
Warning: That file was not part of the compilation database. It may have many parsing errors.