summaryrefslogtreecommitdiff
path: root/src/qdoc/cppcodeparser.cpp
diff options
context:
space:
mode:
authorOswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>2015-10-21 15:51:54 +0200
committerOswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>2015-10-23 10:47:25 +0000
commite15ca59111fe05f5ecc0f7fb48c34b5b22115a12 (patch)
tree1f66c3191f4745dea9bf126b8c22a4d8c5a90c58 /src/qdoc/cppcodeparser.cpp
parent4e2d1893a89ce3dcfaacea091acae1f673c14a9e (diff)
downloadqttools-e15ca59111fe05f5ecc0f7fb48c34b5b22115a12.tar.gz
move qdoc back to qttools
we can do that now, as the bootstrap lib is now a properly exported module, and qmldevtools is now bootstrapped as well. this removes the abomination of a copy of the qml parser in qtbase. unfortunately qtbase/2422251ee5025a067b14b989153764ab36e43f10 is reverted, as qtdeclarative is still missing the respective change. this introduces no regression in discoverability or usability, as a full doc build already needed qttools - for qhelpgenerator. Change-Id: Ic9c4c9732ddf5998637b9e42e27939ba50b31479 Reviewed-by: Jędrzej Nowacki <jedrzej.nowacki@theqtcompany.com> Reviewed-by: Martin Smith <martin.smith@digia.com> Reviewed-by: Lars Knoll <lars.knoll@theqtcompany.com> Reviewed-by: Topi Reiniö <topi.reinio@digia.com>
Diffstat (limited to 'src/qdoc/cppcodeparser.cpp')
-rw-r--r--src/qdoc/cppcodeparser.cpp2611
1 files changed, 2611 insertions, 0 deletions
diff --git a/src/qdoc/cppcodeparser.cpp b/src/qdoc/cppcodeparser.cpp
new file mode 100644
index 000000000..0405cc2c2
--- /dev/null
+++ b/src/qdoc/cppcodeparser.cpp
@@ -0,0 +1,2611 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*
+ cppcodeparser.cpp
+*/
+
+#include <qfile.h>
+#include <stdio.h>
+#include <errno.h>
+#include "codechunk.h"
+#include "config.h"
+#include "cppcodeparser.h"
+#include "tokenizer.h"
+#include "qdocdatabase.h"
+#include <qdebug.h>
+#include "generator.h"
+
+QT_BEGIN_NAMESPACE
+
+/* qmake ignore Q_OBJECT */
+
+static bool inMacroCommand_ = false;
+static bool parsingHeaderFile_ = false;
+QStringList CppCodeParser::exampleFiles;
+QStringList CppCodeParser::exampleDirs;
+CppCodeParser* CppCodeParser::cppParser_ = 0;
+
+/*!
+ The constructor initializes some regular expressions
+ and calls reset().
+ */
+CppCodeParser::CppCodeParser()
+ : varComment("/\\*\\s*([a-zA-Z_0-9]+)\\s*\\*/"), sep("(?:<[^>]+>)?::")
+{
+ reset();
+ cppParser_ = this;
+}
+
+/*!
+ The destructor is trivial.
+ */
+CppCodeParser::~CppCodeParser()
+{
+ // nothing.
+}
+
+/*!
+ The constructor initializes a map of special node types
+ for identifying important nodes. And it initializes
+ some filters for identifying certain kinds of files.
+ */
+void CppCodeParser::initializeParser(const Config &config)
+{
+ CodeParser::initializeParser(config);
+
+ /*
+ All these can appear in a C++ namespace. Don't add
+ anything that can't be in a C++ namespace.
+ */
+ nodeTypeMap.insert(COMMAND_NAMESPACE, Node::Namespace);
+ nodeTypeMap.insert(COMMAND_CLASS, Node::Class);
+ nodeTypeMap.insert(COMMAND_ENUM, Node::Enum);
+ nodeTypeMap.insert(COMMAND_TYPEDEF, Node::Typedef);
+ nodeTypeMap.insert(COMMAND_PROPERTY, Node::Property);
+ nodeTypeMap.insert(COMMAND_VARIABLE, Node::Variable);
+
+ exampleFiles = config.getCanonicalPathList(CONFIG_EXAMPLES);
+ exampleDirs = config.getCanonicalPathList(CONFIG_EXAMPLEDIRS);
+ QStringList exampleFilePatterns = config.getStringList(
+ CONFIG_EXAMPLES + Config::dot + CONFIG_FILEEXTENSIONS);
+
+ if (!exampleFilePatterns.isEmpty())
+ exampleNameFilter = exampleFilePatterns.join(' ');
+ else
+ exampleNameFilter = "*.cpp *.h *.js *.xq *.svg *.xml *.dita *.ui";
+
+ QStringList exampleImagePatterns = config.getStringList(
+ CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS);
+
+ if (!exampleImagePatterns.isEmpty())
+ exampleImageFilter = exampleImagePatterns.join(' ');
+ else
+ exampleImageFilter = "*.png";
+}
+
+/*!
+ Clear the map of common node types and call
+ the same function in the base class.
+ */
+void CppCodeParser::terminateParser()
+{
+ nodeTypeMap.clear();
+ CodeParser::terminateParser();
+}
+
+/*!
+ Returns "Cpp".
+ */
+QString CppCodeParser::language()
+{
+ return "Cpp";
+}
+
+/*!
+ Returns a list of extensions for header files.
+ */
+QStringList CppCodeParser::headerFileNameFilter()
+{
+ return QStringList() << "*.ch" << "*.h" << "*.h++" << "*.hh" << "*.hpp" << "*.hxx";
+}
+
+/*!
+ Returns a list of extensions for source files, i.e. not
+ header files.
+ */
+QStringList CppCodeParser::sourceFileNameFilter()
+{
+ return QStringList() << "*.c++" << "*.cc" << "*.cpp" << "*.cxx" << "*.mm";
+}
+
+/*!
+ Parse the C++ header file identified by \a filePath and add
+ the parsed contents to the database. The \a location is used
+ for reporting errors.
+ */
+void CppCodeParser::parseHeaderFile(const Location& location, const QString& filePath)
+{
+ QFile in(filePath);
+ currentFile_ = filePath;
+ if (!in.open(QIODevice::ReadOnly)) {
+ location.error(tr("Cannot open C++ header file '%1'").arg(filePath));
+ currentFile_.clear();
+ return;
+ }
+
+ reset();
+ Location fileLocation(filePath);
+ Tokenizer fileTokenizer(fileLocation, in);
+ tokenizer = &fileTokenizer;
+ readToken();
+ parsingHeaderFile_ = true;
+ matchDeclList(qdb_->primaryTreeRoot());
+ parsingHeaderFile_ = false;
+ if (!fileTokenizer.version().isEmpty())
+ qdb_->setVersion(fileTokenizer.version());
+ in.close();
+
+ if (fileLocation.fileName() == "qiterator.h")
+ parseQiteratorDotH(location, filePath);
+ currentFile_.clear();
+}
+
+/*!
+ Get ready to parse the C++ cpp file identified by \a filePath
+ and add its parsed contents to the database. \a location is
+ used for reporting errors.
+
+ Call matchDocsAndStuff() to do all the parsing and tree building.
+ */
+void CppCodeParser::parseSourceFile(const Location& location, const QString& filePath)
+{
+ QFile in(filePath);
+ currentFile_ = filePath;
+ if (!in.open(QIODevice::ReadOnly)) {
+ location.error(tr("Cannot open C++ source file '%1' (%2)").arg(filePath).arg(strerror(errno)));
+ currentFile_.clear();
+ return;
+ }
+
+ reset();
+ Location fileLocation(filePath);
+ Tokenizer fileTokenizer(fileLocation, in);
+ tokenizer = &fileTokenizer;
+ readToken();
+
+ /*
+ The set of open namespaces is cleared before parsing
+ each source file. The word "source" here means cpp file.
+ */
+ qdb_->clearOpenNamespaces();
+
+ matchDocsAndStuff();
+ in.close();
+ currentFile_.clear();
+}
+
+/*!
+ This is called after all the C++ header files have been
+ parsed. The most important thing it does is resolve C++
+ class inheritance links in the tree. It also initializes
+ a bunch of other collections.
+ */
+void CppCodeParser::doneParsingHeaderFiles()
+{
+ QMapIterator<QString, QString> i(sequentialIteratorClasses);
+ while (i.hasNext()) {
+ i.next();
+ instantiateIteratorMacro(i.key(), i.value(), sequentialIteratorDefinition);
+ }
+ i = mutableSequentialIteratorClasses;
+ while (i.hasNext()) {
+ i.next();
+ instantiateIteratorMacro(i.key(), i.value(), mutableSequentialIteratorDefinition);
+ }
+ i = associativeIteratorClasses;
+ while (i.hasNext()) {
+ i.next();
+ instantiateIteratorMacro(i.key(), i.value(), associativeIteratorDefinition);
+ }
+ i = mutableAssociativeIteratorClasses;
+ while (i.hasNext()) {
+ i.next();
+ instantiateIteratorMacro(i.key(), i.value(), mutableAssociativeIteratorDefinition);
+ }
+ sequentialIteratorDefinition.clear();
+ mutableSequentialIteratorDefinition.clear();
+ associativeIteratorDefinition.clear();
+ mutableAssociativeIteratorDefinition.clear();
+ sequentialIteratorClasses.clear();
+ mutableSequentialIteratorClasses.clear();
+ associativeIteratorClasses.clear();
+ mutableAssociativeIteratorClasses.clear();
+}
+
+/*!
+ This is called after all the source files (i.e., not the
+ header files) have been parsed. Currently nothing to do.
+ */
+void CppCodeParser::doneParsingSourceFiles()
+{
+ // contents moved to QdocDatabase::resolveIssues()
+}
+
+static QSet<QString> topicCommands_;
+/*!
+ Returns the set of strings reopresenting the topic commands.
+ */
+const QSet<QString>& CppCodeParser::topicCommands()
+{
+ if (topicCommands_.isEmpty()) {
+ topicCommands_ << COMMAND_CLASS
+ << COMMAND_DITAMAP
+ << COMMAND_ENUM
+ << COMMAND_EXAMPLE
+ << COMMAND_EXTERNALPAGE
+ << COMMAND_FILE
+ << COMMAND_FN
+ << COMMAND_GROUP
+ << COMMAND_HEADERFILE
+ << COMMAND_MACRO
+ << COMMAND_MODULE
+ << COMMAND_NAMESPACE
+ << COMMAND_PAGE
+ << COMMAND_PROPERTY
+ << COMMAND_TYPEDEF
+ << COMMAND_VARIABLE
+ << COMMAND_QMLTYPE
+ << COMMAND_QMLPROPERTY
+ << COMMAND_QMLPROPERTYGROUP
+ << COMMAND_QMLATTACHEDPROPERTY
+ << COMMAND_QMLSIGNAL
+ << COMMAND_QMLATTACHEDSIGNAL
+ << COMMAND_QMLMETHOD
+ << COMMAND_QMLATTACHEDMETHOD
+ << COMMAND_QMLBASICTYPE
+ << COMMAND_QMLMODULE
+ << COMMAND_JSTYPE
+ << COMMAND_JSPROPERTY
+ << COMMAND_JSPROPERTYGROUP
+ << COMMAND_JSATTACHEDPROPERTY
+ << COMMAND_JSSIGNAL
+ << COMMAND_JSATTACHEDSIGNAL
+ << COMMAND_JSMETHOD
+ << COMMAND_JSATTACHEDMETHOD
+ << COMMAND_JSBASICTYPE
+ << COMMAND_JSMODULE;
+ }
+ return topicCommands_;
+}
+
+/*!
+ Process the topic \a command found in the \a doc with argument \a arg.
+ */
+Node* CppCodeParser::processTopicCommand(const Doc& doc,
+ const QString& command,
+ const ArgLocPair& arg)
+{
+ ExtraFuncData extra;
+ if (command == COMMAND_FN) {
+ QStringList parentPath;
+ FunctionNode *func = 0;
+ FunctionNode *clone = 0;
+
+ if (!makeFunctionNode(arg.first, &parentPath, &clone, extra) &&
+ !makeFunctionNode("void " + arg.first, &parentPath, &clone, extra)) {
+ doc.startLocation().warning(tr("Invalid syntax in '\\%1'").arg(COMMAND_FN));
+ }
+ else {
+ func = qdb_->findFunctionNode(parentPath, clone);
+ if (func == 0) {
+ if (parentPath.isEmpty() && !lastPath_.isEmpty())
+ func = qdb_->findFunctionNode(lastPath_, clone);
+ }
+
+ /*
+ If the node was not found, then search for it in the
+ open C++ namespaces. We don't expect this search to
+ be necessary often. Nor do we expect it to succeed
+ very often.
+ */
+ if (func == 0)
+ func = qdb_->findNodeInOpenNamespace(parentPath, clone);
+
+ if (func == 0) {
+ doc.location().warning(tr("Cannot find '%1' in '\\%2' %3")
+ .arg(clone->name() + "(...)")
+ .arg(COMMAND_FN)
+ .arg(arg.first),
+ tr("I cannot find any function of that name with the "
+ "specified signature. Make sure that the signature "
+ "is identical to the declaration, including 'const' "
+ "qualifiers."));
+ }
+ else
+ lastPath_ = parentPath;
+ if (func) {
+ func->borrowParameterNames(clone);
+ func->setParentPath(clone->parentPath());
+ }
+ delete clone;
+ }
+ return func;
+ }
+ else if (command == COMMAND_MACRO) {
+ QStringList parentPath;
+ FunctionNode *func = 0;
+
+ extra.root = qdb_->primaryTreeRoot();
+ extra.isMacro = true;
+ if (makeFunctionNode(arg.first, &parentPath, &func, extra)) {
+ if (!parentPath.isEmpty()) {
+ doc.startLocation().warning(tr("Invalid syntax in '\\%1'").arg(COMMAND_MACRO));
+ delete func;
+ func = 0;
+ }
+ else {
+ func->setMetaness(FunctionNode::MacroWithParams);
+ QVector<Parameter> params = func->parameters();
+ for (int i = 0; i < params.size(); ++i) {
+ Parameter &param = params[i];
+ if (param.name().isEmpty() && !param.dataType().isEmpty()
+ && param.dataType() != "...")
+ param = Parameter("", "", param.dataType());
+ }
+ func->setParameters(params);
+ }
+ return func;
+ }
+ else if (QRegExp("[A-Za-z_][A-Za-z0-9_]+").exactMatch(arg.first)) {
+ func = new FunctionNode(qdb_->primaryTreeRoot(), arg.first);
+ func->setAccess(Node::Public);
+ func->setLocation(doc.startLocation());
+ func->setMetaness(FunctionNode::MacroWithoutParams);
+ }
+ else {
+ doc.location().warning(tr("Invalid syntax in '\\%1'").arg(COMMAND_MACRO));
+
+ }
+ return func;
+ }
+ else if (nodeTypeMap.contains(command)) {
+ /*
+ We should only get in here if the command refers to
+ something that can appear in a C++ namespace,
+ i.e. a class, another namespace, an enum, a typedef,
+ a property or a variable. I think these are handled
+ this way to allow the writer to refer to the entity
+ without including the namespace qualifier.
+ */
+ Node::NodeType type = nodeTypeMap[command];
+ QStringList paths = arg.first.split(QLatin1Char(' '));
+ QStringList path = paths[0].split("::");
+ Node *node = 0;
+
+ node = qdb_->findNodeInOpenNamespace(path, type);
+ if (node == 0)
+ node = qdb_->findNodeByNameAndType(path, type);
+ if (node == 0) {
+ doc.location().warning(tr("Cannot find '%1' specified with '\\%2' in any header file")
+ .arg(arg.first).arg(command));
+ lastPath_ = path;
+
+ }
+ else if (node->isAggregate()) {
+ if (type == Node::Namespace) {
+ NamespaceNode* ns = static_cast<NamespaceNode*>(node);
+ ns->markSeen();
+ }
+ /*
+ This treats a class as a namespace.
+ */
+ if ((type == Node::Class) || (type == Node::Namespace)) {
+ if (path.size() > 1) {
+ path.pop_back();
+ QString ns = path.join("::");
+ qdb_->insertOpenNamespace(ns);
+ }
+ }
+ }
+ return node;
+ }
+ else if (command == COMMAND_EXAMPLE) {
+ if (Config::generateExamples) {
+ ExampleNode* en = new ExampleNode(qdb_->primaryTreeRoot(), arg.first);
+ en->setLocation(doc.startLocation());
+ createExampleFileNodes(en);
+ return en;
+ }
+ }
+ else if (command == COMMAND_EXTERNALPAGE) {
+ DocumentNode* dn = new DocumentNode(qdb_->primaryTreeRoot(),
+ arg.first,
+ Node::ExternalPage,
+ Node::ArticlePage);
+ dn->setLocation(doc.startLocation());
+ return dn;
+ }
+ else if (command == COMMAND_FILE) {
+ DocumentNode* dn = new DocumentNode(qdb_->primaryTreeRoot(),
+ arg.first,
+ Node::File,
+ Node::NoPageType);
+ dn->setLocation(doc.startLocation());
+ return dn;
+ }
+ else if (command == COMMAND_HEADERFILE) {
+ DocumentNode* dn = new DocumentNode(qdb_->primaryTreeRoot(),
+ arg.first,
+ Node::HeaderFile,
+ Node::ApiPage);
+ dn->setLocation(doc.startLocation());
+ return dn;
+ }
+ else if (command == COMMAND_GROUP) {
+ CollectionNode* cn = qdb_->addGroup(arg.first);
+ cn->setLocation(doc.startLocation());
+ cn->markSeen();
+ return cn;
+ }
+ else if (command == COMMAND_MODULE) {
+ CollectionNode* cn = qdb_->addModule(arg.first);
+ cn->setLocation(doc.startLocation());
+ cn->markSeen();
+ return cn;
+ }
+ else if (command == COMMAND_QMLMODULE) {
+ QStringList blankSplit = arg.first.split(QLatin1Char(' '));
+ CollectionNode* cn = qdb_->addQmlModule(blankSplit[0]);
+ cn->setLogicalModuleInfo(blankSplit);
+ cn->setLocation(doc.startLocation());
+ cn->markSeen();
+ return cn;
+ }
+ else if (command == COMMAND_JSMODULE) {
+ QStringList blankSplit = arg.first.split(QLatin1Char(' '));
+ CollectionNode* cn = qdb_->addJsModule(blankSplit[0]);
+ cn->setLogicalModuleInfo(blankSplit);
+ cn->setLocation(doc.startLocation());
+ cn->markSeen();
+ return cn;
+ }
+ else if (command == COMMAND_PAGE) {
+ Node::PageType ptype = Node::ArticlePage;
+ QStringList args = arg.first.split(QLatin1Char(' '));
+ if (args.size() > 1) {
+ QString t = args[1].toLower();
+ if (t == "howto")
+ ptype = Node::HowToPage;
+ else if (t == "api")
+ ptype = Node::ApiPage;
+ else if (t == "example")
+ ptype = Node::ExamplePage;
+ else if (t == "overview")
+ ptype = Node::OverviewPage;
+ else if (t == "tutorial")
+ ptype = Node::TutorialPage;
+ else if (t == "faq")
+ ptype = Node::FAQPage;
+ else if (t == "ditamap")
+ ptype = Node::DitaMapPage;
+ }
+ DocumentNode* dn = 0;
+ if (ptype == Node::DitaMapPage)
+ dn = new DitaMapNode(qdb_->primaryTreeRoot(), args[0]);
+ else
+ dn = new DocumentNode(qdb_->primaryTreeRoot(), args[0], Node::Page, ptype);
+ dn->setLocation(doc.startLocation());
+ return dn;
+ }
+ else if (command == COMMAND_DITAMAP) {
+ DocumentNode* dn = new DitaMapNode(qdb_->primaryTreeRoot(), arg.first);
+ dn->setLocation(doc.startLocation());
+ return dn;
+ }
+ else if ((command == COMMAND_QMLTYPE) || (command == COMMAND_JSTYPE)) {
+ QmlTypeNode* qcn = new QmlTypeNode(qdb_->primaryTreeRoot(), arg.first);
+ if (command == COMMAND_JSTYPE)
+ qcn->setGenus(Node::JS);
+ qcn->setLocation(doc.startLocation());
+ return qcn;
+ }
+ else if ((command == COMMAND_QMLBASICTYPE) || (command == COMMAND_JSBASICTYPE)) {
+ QmlBasicTypeNode* n = new QmlBasicTypeNode(qdb_->primaryTreeRoot(), arg.first);
+ if (command == COMMAND_JSBASICTYPE)
+ n->setGenus(Node::JS);
+ n->setLocation(doc.startLocation());
+ return n;
+ }
+ else if ((command == COMMAND_QMLSIGNAL) ||
+ (command == COMMAND_QMLMETHOD) ||
+ (command == COMMAND_QMLATTACHEDSIGNAL) ||
+ (command == COMMAND_QMLATTACHEDMETHOD) ||
+ (command == COMMAND_JSSIGNAL) ||
+ (command == COMMAND_JSMETHOD) ||
+ (command == COMMAND_JSATTACHEDSIGNAL) ||
+ (command == COMMAND_JSATTACHEDMETHOD)) {
+ QString module;
+ QString qmlTypeName;
+ QString type;
+ if (splitQmlMethodArg(arg.first, type, module, qmlTypeName)) {
+ QmlTypeNode* qmlType = qdb_->findQmlType(module, qmlTypeName);
+ if (qmlType) {
+ bool attached = false;
+ Node::NodeType nodeType = Node::QmlMethod;
+ if ((command == COMMAND_QMLSIGNAL) ||
+ (command == COMMAND_JSSIGNAL))
+ nodeType = Node::QmlSignal;
+ else if ((command == COMMAND_QMLATTACHEDSIGNAL) ||
+ (command == COMMAND_JSATTACHEDSIGNAL)) {
+ nodeType = Node::QmlSignal;
+ attached = true;
+ }
+ else if ((command == COMMAND_QMLMETHOD) ||
+ (command == COMMAND_JSMETHOD)) {
+ // do nothing
+ }
+ else if ((command == COMMAND_QMLATTACHEDMETHOD) ||
+ (command == COMMAND_JSATTACHEDMETHOD))
+ attached = true;
+ else
+ return 0; // never get here.
+ FunctionNode* fn = makeFunctionNode(doc,
+ arg.first,
+ qmlType,
+ nodeType,
+ attached,
+ command);
+ if (fn) {
+ fn->setLocation(doc.startLocation());
+ if ((command == COMMAND_JSSIGNAL) ||
+ (command == COMMAND_JSMETHOD) ||
+ (command == COMMAND_JSATTACHEDSIGNAL) ||
+ (command == COMMAND_JSATTACHEDMETHOD))
+ fn->setGenus(Node::JS);
+ }
+ return fn;
+ }
+ }
+ }
+ return 0;
+}
+
+/*!
+ A QML property group argument has the form...
+
+ <QML-module>::<QML-type>::<name>
+
+ This function splits the argument into those parts.
+ A <QML-module> is the QML equivalent of a C++ namespace.
+ So this function splits \a arg on "::" and stores the
+ parts in \a module, \a qmlTypeName, and \a name, and returns
+ true. If any part is not found, a qdoc warning is emitted
+ and false is returned.
+ */
+bool CppCodeParser::splitQmlPropertyGroupArg(const QString& arg,
+ QString& module,
+ QString& qmlTypeName,
+ QString& name)
+{
+ QStringList colonSplit = arg.split("::");
+ if (colonSplit.size() == 3) {
+ module = colonSplit[0];
+ qmlTypeName = colonSplit[1];
+ name = colonSplit[2];
+ return true;
+ }
+ QString msg = "Unrecognizable QML module/component qualifier for " + arg;
+ location().warning(tr(msg.toLatin1().data()));
+ return false;
+}
+
+/*!
+ A QML property argument has the form...
+
+ <type> <QML-type>::<name>
+ <type> <QML-module>::<QML-type>::<name>
+
+ This function splits the argument into one of those
+ two forms. The three part form is the old form, which
+ was used before the creation of Qt Quick 2 and Qt
+ Components. A <QML-module> is the QML equivalent of a
+ C++ namespace. So this function splits \a arg on "::"
+ and stores the parts in \a type, \a module, \a qmlTypeName,
+ and \a name, and returns \c true. If any part other than
+ \a module is not found, a qdoc warning is emitted and
+ false is returned.
+
+ \note The two QML types \e{Component} and \e{QtObject}
+ never have a module qualifier.
+ */
+bool CppCodeParser::splitQmlPropertyArg(const QString& arg,
+ QString& type,
+ QString& module,
+ QString& qmlTypeName,
+ QString& name)
+{
+ QStringList blankSplit = arg.split(QLatin1Char(' '));
+ if (blankSplit.size() > 1) {
+ type = blankSplit[0];
+ QStringList colonSplit(blankSplit[1].split("::"));
+ if (colonSplit.size() == 3) {
+ module = colonSplit[0];
+ qmlTypeName = colonSplit[1];
+ name = colonSplit[2];
+ return true;
+ }
+ if (colonSplit.size() == 2) {
+ module.clear();
+ qmlTypeName = colonSplit[0];
+ name = colonSplit[1];
+ return true;
+ }
+ QString msg = "Unrecognizable QML module/component qualifier for " + arg;
+ location().warning(tr(msg.toLatin1().data()));
+ }
+ else {
+ QString msg = "Missing property type for " + arg;
+ location().warning(tr(msg.toLatin1().data()));
+ }
+ return false;
+}
+
+/*!
+ A QML signal or method argument has the form...
+
+ <type> <QML-type>::<name>(<param>, <param>, ...)
+ <type> <QML-module>::<QML-type>::<name>(<param>, <param>, ...)
+
+ This function splits the \a{arg}ument into one of those
+ two forms, sets \a type, \a module, and \a qmlTypeName,
+ and returns true. If the argument doesn't match either
+ form, an error message is emitted and false is returned.
+
+ \note The two QML types \e{Component} and \e{QtObject} never
+ have a module qualifier.
+ */
+bool CppCodeParser::splitQmlMethodArg(const QString& arg,
+ QString& type,
+ QString& module,
+ QString& qmlTypeName)
+{
+ QString name;
+ int leftParen = arg.indexOf(QChar('('));
+ if (leftParen > 0)
+ name = arg.left(leftParen);
+ else
+ name = arg;
+ int firstBlank = name.indexOf(QChar(' '));
+ if (firstBlank > 0) {
+ type = name.left(firstBlank);
+ name = name.right(name.length() - firstBlank - 1);
+ }
+ else
+ type.clear();
+
+ QStringList colonSplit(name.split("::"));
+ if (colonSplit.size() > 1) {
+ if (colonSplit.size() > 2) {
+ module = colonSplit[0];
+ qmlTypeName = colonSplit[1];
+ }
+ else {
+ module.clear();
+ qmlTypeName = colonSplit[0];
+ }
+ return true;
+ }
+ QString msg = "Unrecognizable QML module/component qualifier for " + arg;
+ location().warning(tr(msg.toLatin1().data()));
+ return false;
+}
+
+/*!
+ Process the topic \a command group found in the \a doc with arguments \a args.
+
+ Currently, this function is called only for \e{qmlproperty}
+ and \e{qmlattachedproperty}.
+ */
+void CppCodeParser::processQmlProperties(const Doc& doc,
+ NodeList& nodes,
+ DocList& docs,
+ bool jsProps)
+{
+ QString arg;
+ QString type;
+ QString topic;
+ QString module;
+ QString qmlTypeName;
+ QString property;
+ QmlPropertyNode* qpn = 0;
+ QmlTypeNode* qmlType = 0;
+ QmlPropertyGroupNode* qpgn = 0;
+
+ Topic qmlPropertyGroupTopic;
+ const TopicList& topics = doc.topicsUsed();
+ for (int i=0; i<topics.size(); ++i) {
+ if ((topics.at(i).topic == COMMAND_QMLPROPERTYGROUP) ||
+ (topics.at(i).topic == COMMAND_JSPROPERTYGROUP)) {
+ qmlPropertyGroupTopic = topics.at(i);
+ break;
+ }
+ }
+ if (qmlPropertyGroupTopic.isEmpty() && topics.size() > 1) {
+ qmlPropertyGroupTopic = topics.at(0);
+ if (jsProps)
+ qmlPropertyGroupTopic.topic = COMMAND_JSPROPERTYGROUP;
+ else
+ qmlPropertyGroupTopic.topic = COMMAND_QMLPROPERTYGROUP;
+ arg = qmlPropertyGroupTopic.args;
+ if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property)) {
+ int i = property.indexOf('.');
+ if (i != -1) {
+ property = property.left(i);
+ qmlPropertyGroupTopic.args = module + "::" + qmlTypeName + "::" + property;
+ doc.location().warning(tr("No QML property group command found; using \\%1 %2")
+ .arg(COMMAND_QMLPROPERTYGROUP).arg(qmlPropertyGroupTopic.args));
+ }
+ else {
+ /*
+ Assumption: No '.' in the property name
+ means there is no property group.
+ */
+ qmlPropertyGroupTopic.clear();
+ }
+ }
+ }
+
+ if (!qmlPropertyGroupTopic.isEmpty()) {
+ arg = qmlPropertyGroupTopic.args;
+ if (splitQmlPropertyGroupArg(arg, module, qmlTypeName, property)) {
+ qmlType = qdb_->findQmlType(module, qmlTypeName);
+ if (qmlType) {
+ qpgn = new QmlPropertyGroupNode(qmlType, property);
+ qpgn->setLocation(doc.startLocation());
+ if (jsProps)
+ qpgn->setGenus(Node::JS);
+ nodes.append(qpgn);
+ docs.append(doc);
+ }
+ }
+ }
+ for (int i=0; i<topics.size(); ++i) {
+ if (topics.at(i).topic == COMMAND_QMLPROPERTYGROUP) {
+ continue;
+ }
+ topic = topics.at(i).topic;
+ arg = topics.at(i).args;
+ if ((topic == COMMAND_QMLPROPERTY) || (topic == COMMAND_QMLATTACHEDPROPERTY) ||
+ (topic == COMMAND_JSPROPERTY) || (topic == COMMAND_JSATTACHEDPROPERTY)) {
+ bool attached = ((topic == COMMAND_QMLATTACHEDPROPERTY) ||
+ (topic == COMMAND_JSATTACHEDPROPERTY));
+ if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property)) {
+ qmlType = qdb_->findQmlType(module, qmlTypeName);
+ if (qmlType) {
+ if (qmlType->hasQmlProperty(property, attached) != 0) {
+ QString msg = tr("QML property documented multiple times: '%1'").arg(arg);
+ doc.startLocation().warning(msg);
+ }
+ else if (qpgn) {
+ qpn = new QmlPropertyNode(qpgn, property, type, attached);
+ qpn->setLocation(doc.startLocation());
+ if (jsProps)
+ qpn->setGenus(Node::JS);
+ }
+ else {
+ qpn = new QmlPropertyNode(qmlType, property, type, attached);
+ qpn->setLocation(doc.startLocation());
+ if (jsProps)
+ qpn->setGenus(Node::JS);
+ nodes.append(qpn);
+ docs.append(doc);
+ }
+ }
+ }
+ } else if (qpgn) {
+ doc.startLocation().warning(
+ tr("Invalid use of '\\%1'; not allowed in a '\\%2'").arg(
+ topic, qmlPropertyGroupTopic.topic));
+ }
+ }
+}
+
+static QSet<QString> otherMetaCommands_;
+/*!
+ Returns the set of strings representing the common metacommands
+ plus some other metacommands.
+ */
+const QSet<QString>& CppCodeParser::otherMetaCommands()
+{
+ if (otherMetaCommands_.isEmpty()) {
+ otherMetaCommands_ = commonMetaCommands();
+ otherMetaCommands_ << COMMAND_INHEADERFILE
+ << COMMAND_OVERLOAD
+ << COMMAND_REIMP
+ << COMMAND_RELATES
+ << COMMAND_CONTENTSPAGE
+ << COMMAND_NEXTPAGE
+ << COMMAND_PREVIOUSPAGE
+ << COMMAND_INDEXPAGE
+ << COMMAND_STARTPAGE
+ << COMMAND_QMLINHERITS
+ << COMMAND_QMLINSTANTIATES
+ << COMMAND_QMLDEFAULT
+ << COMMAND_QMLREADONLY
+ << COMMAND_QMLABSTRACT
+ << COMMAND_ABSTRACT;
+ }
+ return otherMetaCommands_;
+}
+
+/*!
+ Process the metacommand \a command in the context of the
+ \a node associated with the topic command and the \a doc.
+ \a arg is the argument to the metacommand.
+ */
+void CppCodeParser::processOtherMetaCommand(const Doc& doc,
+ const QString& command,
+ const ArgLocPair& argLocPair,
+ Node *node)
+{
+ QString arg = argLocPair.first;
+ if (command == COMMAND_INHEADERFILE) {
+ if (node != 0 && node->isAggregate()) {
+ ((Aggregate *) node)->addInclude(arg);
+ }
+ else {
+ doc.location().warning(tr("Ignored '\\%1'").arg(COMMAND_INHEADERFILE));
+ }
+ }
+ else if (command == COMMAND_OVERLOAD) {
+ if (node && node->isFunction())
+ ((FunctionNode *) node)->setOverloadFlag(true);
+ else
+ doc.location().warning(tr("Ignored '\\%1'").arg(COMMAND_OVERLOAD));
+ }
+ else if (command == COMMAND_REIMP) {
+ if (node != 0 && node->parent() && !node->parent()->isInternal()) {
+ if (node->type() == Node::Function) {
+ FunctionNode *func = (FunctionNode *) node;
+ const FunctionNode *from = func->reimplementedFrom();
+ if (from == 0) {
+ doc.location().warning(tr("Cannot find base function for '\\%1' in %2()")
+ .arg(COMMAND_REIMP).arg(node->name()),
+ tr("The function either doesn't exist in any "
+ "base class with the same signature or it "
+ "exists but isn't virtual."));
+ }
+ /*
+ Ideally, we would enable this check to warn whenever
+ \reimp is used incorrectly, and only make the node
+ internal if the function is a reimplementation of
+ another function in a base class.
+ */
+ else if (from->access() == Node::Private
+ || from->parent()->access() == Node::Private) {
+ doc.location().warning(tr("'\\%1' in %2() should be '\\internal' "
+ "because its base function is private "
+ "or internal").arg(COMMAND_REIMP).arg(node->name()));
+ }
+ func->setReimplemented(true);
+ }
+ else {
+ doc.location().warning(tr("Ignored '\\%1' in %2").arg(COMMAND_REIMP).arg(node->name()));
+ }
+ }
+ }
+ else if (command == COMMAND_RELATES) {
+ QStringList path = arg.split("::");
+ Node* n = qdb_->findRelatesNode(path);
+ if (!n) {
+ // Store just a string to write to the index file
+ if (Generator::preparing())
+ node->setRelates(arg);
+ else
+ doc.location().warning(tr("Cannot find '%1' in '\\%2'").arg(arg).arg(COMMAND_RELATES));
+
+ }
+ else if (node->parent() != n)
+ node->setRelates(static_cast<Aggregate*>(n));
+ else
+ doc.location().warning(tr("Invalid use of '\\%1' (already a member of '%2')")
+ .arg(COMMAND_RELATES, arg));
+ }
+ else if (command == COMMAND_CONTENTSPAGE) {
+ setLink(node, Node::ContentsLink, arg);
+ }
+ else if (command == COMMAND_NEXTPAGE) {
+ setLink(node, Node::NextLink, arg);
+ }
+ else if (command == COMMAND_PREVIOUSPAGE) {
+ setLink(node, Node::PreviousLink, arg);
+ }
+ else if (command == COMMAND_INDEXPAGE) {
+ setLink(node, Node::IndexLink, arg);
+ }
+ else if (command == COMMAND_STARTPAGE) {
+ setLink(node, Node::StartLink, arg);
+ }
+ else if (command == COMMAND_QMLINHERITS) {
+ if (node->name() == arg)
+ doc.location().warning(tr("%1 tries to inherit itself").arg(arg));
+ else if (node->isQmlType() || node->isJsType()) {
+ QmlTypeNode* qmlType = static_cast<QmlTypeNode*>(node);
+ qmlType->setQmlBaseName(arg);
+ QmlTypeNode::addInheritedBy(arg,node);
+ }
+ }
+ else if (command == COMMAND_QMLINSTANTIATES) {
+ if (node->isQmlType() || node->isJsType()) {
+ ClassNode* classNode = qdb_->findClassNode(arg.split("::"));
+ if (classNode)
+ node->setClassNode(classNode);
+ else
+ doc.location().warning(tr("C++ class %1 not found: \\instantiates %1").arg(arg));
+ }
+ else
+ doc.location().warning(tr("\\instantiates is only allowed in \\qmltype"));
+ }
+ else if (command == COMMAND_QMLDEFAULT) {
+ if (node->type() == Node::QmlProperty) {
+ QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node);
+ qpn->setDefault();
+ }
+ else if (node->type() == Node::QmlPropertyGroup) {
+ QmlPropertyGroupNode* qpgn = static_cast<QmlPropertyGroupNode*>(node);
+ NodeList::ConstIterator p = qpgn->childNodes().constBegin();
+ while (p != qpgn->childNodes().constEnd()) {
+ if ((*p)->type() == Node::QmlProperty) {
+ QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(*p);
+ qpn->setDefault();
+ }
+ ++p;
+ }
+ }
+ }
+ else if (command == COMMAND_QMLREADONLY) {
+ if (node->type() == Node::QmlProperty) {
+ QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node);
+ qpn->setReadOnly(1);
+ }
+ else if (node->type() == Node::QmlPropertyGroup) {
+ QmlPropertyGroupNode* qpgn = static_cast<QmlPropertyGroupNode*>(node);
+ NodeList::ConstIterator p = qpgn->childNodes().constBegin();
+ while (p != qpgn->childNodes().constEnd()) {
+ if ((*p)->type() == Node::QmlProperty) {
+ QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(*p);
+ qpn->setReadOnly(1);
+ }
+ ++p;
+ }
+ }
+ }
+ else if ((command == COMMAND_QMLABSTRACT) || (command == COMMAND_ABSTRACT)) {
+ if (node->isQmlType() || node->isJsType())
+ node->setAbstract(true);
+ }
+ else {
+ processCommonMetaCommand(doc.location(),command,argLocPair,node);
+ }
+}
+
+/*!
+ The topic command has been processed resulting in the \a doc
+ and \a node passed in here. Process the other meta commands,
+ which are found in \a doc, in the context of the topic \a node.
+ */
+void CppCodeParser::processOtherMetaCommands(const Doc& doc, Node *node)
+{
+ const QSet<QString> metaCommands = doc.metaCommandsUsed();
+ QSet<QString>::ConstIterator cmd = metaCommands.constBegin();
+ while (cmd != metaCommands.constEnd()) {
+ ArgList args = doc.metaCommandArgs(*cmd);
+ ArgList::ConstIterator arg = args.constBegin();
+ while (arg != args.constEnd()) {
+ processOtherMetaCommand(doc, *cmd, *arg, node);
+ ++arg;
+ }
+ ++cmd;
+ }
+}
+
+/*!
+ Resets the C++ code parser to its default initialized state.
+ */
+void CppCodeParser::reset()
+{
+ tokenizer = 0;
+ tok = 0;
+ access = Node::Public;
+ metaness_ = FunctionNode::Plain;
+ lastPath_.clear();
+ physicalModuleName.clear();
+}
+
+/*!
+ Get the next token from the file being parsed and store it
+ in the token variable.
+ */
+void CppCodeParser::readToken()
+{
+ tok = tokenizer->getToken();
+}
+
+/*!
+ Return the current location in the file being parsed,
+ i.e. the file name, line number, and column number.
+ */
+const Location& CppCodeParser::location()
+{
+ return tokenizer->location();
+}
+
+/*!
+ Return the previous string read from the file being parsed.
+ */
+QString CppCodeParser::previousLexeme()
+{
+ return tokenizer->previousLexeme();
+}
+
+/*!
+ Return the current string string from the file being parsed.
+ */
+QString CppCodeParser::lexeme()
+{
+ return tokenizer->lexeme();
+}
+
+bool CppCodeParser::match(int target)
+{
+ if (tok == target) {
+ readToken();
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Skip to \a target. If \a target is found before the end
+ of input, return true. Otherwise return false.
+ */
+bool CppCodeParser::skipTo(int target)
+{
+ while ((tok != Tok_Eoi) && (tok != target))
+ readToken();
+ return tok == target;
+}
+
+/*!
+ If the current token is one of the keyword thingees that
+ are used in Qt, skip over it to the next token and return
+ true. Otherwise just return false without reading the
+ next token.
+ */
+bool CppCodeParser::matchCompat()
+{
+ switch (tok) {
+ case Tok_QT_COMPAT:
+ case Tok_QT_COMPAT_CONSTRUCTOR:
+ case Tok_QT_DEPRECATED:
+ case Tok_QT_MOC_COMPAT:
+ case Tok_QT3_SUPPORT:
+ case Tok_QT3_SUPPORT_CONSTRUCTOR:
+ case Tok_QT3_MOC_SUPPORT:
+ readToken();
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool CppCodeParser::matchModuleQualifier(QString& name)
+{
+ bool matches = (lexeme() == QString('.'));
+ if (matches) {
+ do {
+ name += lexeme();
+ readToken();
+ } while ((tok == Tok_Ident) || (lexeme() == QString('.')));
+ }
+ return matches;
+}
+
+bool CppCodeParser::matchTemplateAngles(CodeChunk *dataType)
+{
+ bool matches = (tok == Tok_LeftAngle);
+ if (matches) {
+ int leftAngleDepth = 0;
+ int parenAndBraceDepth = 0;
+ do {
+ if (tok == Tok_LeftAngle) {
+ leftAngleDepth++;
+ }
+ else if (tok == Tok_RightAngle) {
+ leftAngleDepth--;
+ }
+ else if (tok == Tok_LeftParen || tok == Tok_LeftBrace) {
+ ++parenAndBraceDepth;
+ }
+ else if (tok == Tok_RightParen || tok == Tok_RightBrace) {
+ if (--parenAndBraceDepth < 0)
+ return false;
+ }
+ if (dataType != 0)
+ dataType->append(lexeme());
+ readToken();
+ } while (leftAngleDepth > 0 && tok != Tok_Eoi);
+ }
+ return matches;
+}
+
+/*
+ This function is no longer used.
+ */
+bool CppCodeParser::matchTemplateHeader()
+{
+ readToken();
+ return matchTemplateAngles();
+}
+
+bool CppCodeParser::matchDataType(CodeChunk *dataType, QString *var)
+{
+ /*
+ This code is really hard to follow... sorry. The loop is there to match
+ Alpha::Beta::Gamma::...::Omega.
+ */
+ for (;;) {
+ bool virgin = true;
+
+ if (tok != Tok_Ident) {
+ /*
+ There is special processing for 'Foo::operator int()'
+ and such elsewhere. This is the only case where we
+ return something with a trailing gulbrandsen ('Foo::').
+ */
+ if (tok == Tok_operator)
+ return true;
+
+ /*
+ People may write 'const unsigned short' or
+ 'short unsigned const' or any other permutation.
+ */
+ while (match(Tok_const) || match(Tok_volatile))
+ dataType->append(previousLexeme());
+ while (match(Tok_signed) || match(Tok_unsigned) ||
+ match(Tok_short) || match(Tok_long) || match(Tok_int64)) {
+ dataType->append(previousLexeme());
+ virgin = false;
+ }
+ while (match(Tok_const) || match(Tok_volatile))
+ dataType->append(previousLexeme());
+
+ if (match(Tok_Tilde))
+ dataType->append(previousLexeme());
+ }
+
+ if (virgin) {
+ if (match(Tok_Ident)) {
+ /*
+ This is a hack until we replace this "parser"
+ with the real one used in Qt Creator.
+ */
+ if (!inMacroCommand_ && lexeme() == "(" &&
+ ((previousLexeme() == "QT_PREPEND_NAMESPACE") || (previousLexeme() == "NS"))) {
+ readToken();
+ readToken();
+ dataType->append(previousLexeme());
+ readToken();
+ }
+ else
+ dataType->append(previousLexeme());
+ }
+ else if (match(Tok_void) || match(Tok_int) || match(Tok_char) ||
+ match(Tok_double) || match(Tok_Ellipsis)) {
+ dataType->append(previousLexeme());
+ }
+ else {
+ return false;
+ }
+ }
+ else if (match(Tok_int) || match(Tok_char) || match(Tok_double)) {
+ dataType->append(previousLexeme());
+ }
+
+ matchTemplateAngles(dataType);
+
+ while (match(Tok_const) || match(Tok_volatile))
+ dataType->append(previousLexeme());
+
+ if (match(Tok_Gulbrandsen))
+ dataType->append(previousLexeme());
+ else
+ break;
+ }
+
+ while (match(Tok_Ampersand) || match(Tok_Aster) || match(Tok_const) ||
+ match(Tok_Caret))
+ dataType->append(previousLexeme());
+
+ if (match(Tok_LeftParenAster)) {
+ /*
+ A function pointer. This would be rather hard to handle without a
+ tokenizer hack, because a type can be followed with a left parenthesis
+ in some cases (e.g., 'operator int()'). The tokenizer recognizes '(*'
+ as a single token.
+ */
+ dataType->append(previousLexeme());
+ dataType->appendHotspot();
+ if (var != 0 && match(Tok_Ident))
+ *var = previousLexeme();
+ if (!match(Tok_RightParen) || tok != Tok_LeftParen) {
+ return false;
+ }
+ dataType->append(previousLexeme());
+
+ int parenDepth0 = tokenizer->parenDepth();
+ while (tokenizer->parenDepth() >= parenDepth0 && tok != Tok_Eoi) {
+ dataType->append(lexeme());
+ readToken();
+ }
+ if (match(Tok_RightParen))
+ dataType->append(previousLexeme());
+ }
+ else {
+ /*
+ The common case: Look for an optional identifier, then for
+ some array brackets.
+ */
+ dataType->appendHotspot();
+
+ if (var != 0) {
+ if (match(Tok_Ident)) {
+ *var = previousLexeme();
+ }
+ else if (match(Tok_Comment)) {
+ /*
+ A neat hack: Commented-out parameter names are
+ recognized by qdoc. It's impossible to illustrate
+ here inside a C-style comment, because it requires
+ an asterslash. It's also impossible to illustrate
+ inside a C++-style comment, because the explanation
+ does not fit on one line.
+ */
+ if (varComment.exactMatch(previousLexeme()))
+ *var = varComment.cap(1);
+ }
+ }
+
+ if (tok == Tok_LeftBracket) {
+ int bracketDepth0 = tokenizer->bracketDepth();
+ while ((tokenizer->bracketDepth() >= bracketDepth0 &&
+ tok != Tok_Eoi) ||
+ tok == Tok_RightBracket) {
+ dataType->append(lexeme());
+ readToken();
+ }
+ }
+ }
+ return true;
+}
+
+/*!
+ Parse the next function parameter, if there is one, and
+ append it to parameter vector \a pvect. Return true if
+ a parameter is parsed and appended to \a pvect.
+ Otherwise return false.
+ */
+bool CppCodeParser::matchParameter(QVector<Parameter>& pvect, bool& isQPrivateSignal)
+{
+ if (match(Tok_QPrivateSignal)) {
+ isQPrivateSignal = true;
+ return true;
+ }
+
+ Parameter p;
+ CodeChunk chunk;
+ if (!matchDataType(&chunk, &p.name_)) {
+ return false;
+ }
+ p.dataType_ = chunk.toString();
+ chunk.clear();
+ match(Tok_Comment);
+ if (match(Tok_Equal)) {
+ int pdepth = tokenizer->parenDepth();
+ while (tokenizer->parenDepth() >= pdepth &&
+ (tok != Tok_Comma || (tokenizer->parenDepth() > pdepth)) &&
+ tok != Tok_Eoi) {
+ chunk.append(lexeme());
+ readToken();
+ }
+ }
+ p.defaultValue_ = chunk.toString();
+ pvect.append(p);
+ return true;
+}
+
+/*!
+ If the current token is any of several function modifiers,
+ return that token value after reading the next token. If it
+ is not one of the function modieifer tokens, return -1 but
+ don\t read the next token.
+ */
+int CppCodeParser::matchFunctionModifier()
+{
+ switch (tok) {
+ case Tok_friend:
+ case Tok_inline:
+ case Tok_explicit:
+ case Tok_static:
+ case Tok_QT_DEPRECATED:
+ readToken();
+ return tok;
+ case Tok_QT_COMPAT:
+ case Tok_QT_COMPAT_CONSTRUCTOR:
+ case Tok_QT_MOC_COMPAT:
+ case Tok_QT3_SUPPORT:
+ case Tok_QT3_SUPPORT_CONSTRUCTOR:
+ case Tok_QT3_MOC_SUPPORT:
+ readToken();
+ return Tok_QT_COMPAT;
+ default:
+ break;
+ }
+ return -1;
+}
+
+bool CppCodeParser::matchFunctionDecl(Aggregate *parent,
+ QStringList *parentPathPtr,
+ FunctionNode **funcPtr,
+ const QString &templateStuff,
+ ExtraFuncData& extra)
+{
+ CodeChunk returnType;
+ QStringList parentPath;
+ QString name;
+
+ bool matched_QT_DEPRECATED = false;
+ bool matched_friend = false;
+ bool matched_static = false;
+ bool matched_inline = false;
+ bool matched_explicit = false;
+ bool matched_compat = false;
+
+ int token = tok;
+ while (token != -1) {
+ switch (token) {
+ case Tok_friend:
+ matched_friend = true;
+ break;
+ case Tok_inline:
+ matched_inline = true;
+ break;
+ case Tok_explicit:
+ matched_explicit = true;
+ break;
+ case Tok_static:
+ matched_static = true;
+ break;
+ case Tok_QT_DEPRECATED:
+ // no break here.
+ matched_QT_DEPRECATED = true;
+ case Tok_QT_COMPAT:
+ matched_compat = true;
+ break;
+ }
+ token = matchFunctionModifier();
+ }
+
+ FunctionNode::Virtualness virtuality = FunctionNode::NonVirtual;
+ if (match(Tok_virtual)) {
+ virtuality = FunctionNode::NormalVirtual;
+ if (!matched_compat)
+ matched_compat = matchCompat();
+ }
+
+ if (!matchDataType(&returnType)) {
+ if (tokenizer->parsingFnOrMacro()
+ && (match(Tok_Q_DECLARE_FLAGS) ||
+ match(Tok_Q_PROPERTY) ||
+ match(Tok_Q_PRIVATE_PROPERTY)))
+ returnType = CodeChunk(previousLexeme());
+ else {
+ return false;
+ }
+ }
+
+ if (returnType.toString() == "QBool")
+ returnType = CodeChunk("bool");
+
+ if (!matched_compat)
+ matched_compat = matchCompat();
+
+ if (tok == Tok_operator &&
+ (returnType.toString().isEmpty() ||
+ returnType.toString().endsWith("::"))) {
+ // 'QString::operator const char *()'
+ parentPath = returnType.toString().split(sep);
+ parentPath.removeAll(QString());
+ returnType = CodeChunk();
+ readToken();
+
+ CodeChunk restOfName;
+ if (tok != Tok_Tilde && matchDataType(&restOfName)) {
+ name = "operator " + restOfName.toString();
+ }
+ else {
+ name = previousLexeme() + lexeme();
+ readToken();
+ while (tok != Tok_LeftParen && tok != Tok_Eoi) {
+ name += lexeme();
+ readToken();
+ }
+ }
+ if (tok != Tok_LeftParen) {
+ return false;
+ }
+ }
+ else if (tok == Tok_LeftParen) {
+ // constructor or destructor
+ parentPath = returnType.toString().split(sep);
+ if (!parentPath.isEmpty()) {
+ name = parentPath.last();
+ parentPath.erase(parentPath.end() - 1);
+ }
+ returnType = CodeChunk();
+ }
+ else {
+ while (match(Tok_Ident)) {
+ name = previousLexeme();
+ /*
+ This is a hack to let QML module identifiers through.
+ */
+ matchModuleQualifier(name);
+ matchTemplateAngles();
+
+ if (match(Tok_Gulbrandsen))
+ parentPath.append(name);
+ else
+ break;
+ }
+
+ if (tok == Tok_operator) {
+ name = lexeme();
+ readToken();
+ while (tok != Tok_Eoi) {
+ name += lexeme();
+ readToken();
+ if (tok == Tok_LeftParen)
+ break;
+ }
+ }
+ if (parent && (tok == Tok_Semicolon ||
+ tok == Tok_LeftBracket ||
+ tok == Tok_Colon)
+ && access != Node::Private) {
+ if (tok == Tok_LeftBracket) {
+ returnType.appendHotspot();
+
+ int bracketDepth0 = tokenizer->bracketDepth();
+ while ((tokenizer->bracketDepth() >= bracketDepth0 &&
+ tok != Tok_Eoi) ||
+ tok == Tok_RightBracket) {
+ returnType.append(lexeme());
+ readToken();
+ }
+ if (tok != Tok_Semicolon) {
+ return false;
+ }
+ }
+ else if (tok == Tok_Colon) {
+ returnType.appendHotspot();
+
+ while (tok != Tok_Semicolon && tok != Tok_Eoi) {
+ returnType.append(lexeme());
+ readToken();
+ }
+ if (tok != Tok_Semicolon) {
+ return false;
+ }
+ }
+
+ VariableNode *var = new VariableNode(parent, name);
+ var->setAccess(access);
+ var->setLocation(location());
+ var->setLeftType(returnType.left());
+ var->setRightType(returnType.right());
+ if (matched_compat)
+ var->setStatus(Node::Compat);
+ var->setStatic(matched_static);
+ return false;
+ }
+ if (tok != Tok_LeftParen)
+ return false;
+ }
+ readToken();
+
+ // A left paren was seen. Parse the parameters
+ bool isQPrivateSignal = false;
+ QVector<Parameter> pvect;
+ if (tok != Tok_RightParen) {
+ do {
+ if (!matchParameter(pvect, isQPrivateSignal))
+ return false;
+ } while (match(Tok_Comma));
+ }
+ // The parameters must end with a right paren
+ if (!match(Tok_RightParen))
+ return false;
+
+ // look for const
+ bool matchedConst = match(Tok_const);
+
+ // look for 0 indicating pure virtual
+ if (match(Tok_Equal) && match(Tok_Number))
+ virtuality = FunctionNode::PureVirtual;
+
+ // look for colon indicating ctors which must be skipped
+ if (match(Tok_Colon)) {
+ while (tok != Tok_LeftBrace && tok != Tok_Eoi)
+ readToken();
+ }
+
+ // If no ';' expect a body, which must be skipped.
+ bool body_expected = false;
+ bool body_present = false;
+ if (!match(Tok_Semicolon) && tok != Tok_Eoi) {
+ body_expected = true;
+ int nesting = tokenizer->braceDepth();
+ if (!match(Tok_LeftBrace))
+ return false;
+ // skip the body
+ while (tokenizer->braceDepth() >= nesting && tok != Tok_Eoi)
+ readToken();
+ body_present = true;
+ match(Tok_RightBrace);
+ }
+
+ FunctionNode *func = 0;
+ bool createFunctionNode = false;
+ if (parsingHeaderFile_) {
+ if (matched_friend) {
+ if (matched_inline) {
+ // nothing yet
+ }
+ if (body_present) {
+ if (body_expected) {
+ // nothing yet
+ }
+ createFunctionNode = true;
+ if (parent && parent->parent())
+ parent = parent->parent();
+ else
+ return false;
+ }
+ }
+ else
+ createFunctionNode = true;
+ }
+ else
+ createFunctionNode = true;
+
+ if (createFunctionNode) {
+ func = new FunctionNode(extra.type, parent, name, extra.isAttached);
+ if (matched_friend)
+ access = Node::Public;
+ func->setAccess(access);
+ func->setLocation(location());
+ func->setReturnType(returnType.toString());
+ func->setParentPath(parentPath);
+ func->setTemplateStuff(templateStuff);
+ if (matched_compat)
+ func->setStatus(Node::Compat);
+ if (matched_QT_DEPRECATED)
+ func->setStatus(Node::Deprecated);
+ if (matched_explicit) { /* What can be done? */ }
+ func->setMetaness(metaness_);
+ if (parent) {
+ if (name == parent->name())
+ func->setMetaness(FunctionNode::Ctor);
+ else if (name.startsWith(QLatin1Char('~')))
+ func->setMetaness(FunctionNode::Dtor);
+ }
+ func->setStatic(matched_static);
+ func->setConst(matchedConst);
+ func->setVirtualness(virtuality);
+ if (isQPrivateSignal)
+ func->setPrivateSignal();
+ if (!pvect.isEmpty()) {
+ func->setParameters(pvect);
+ }
+ }
+ if (parentPathPtr != 0)
+ *parentPathPtr = parentPath;
+ if (funcPtr != 0)
+ *funcPtr = func;
+ return true;
+}
+
+bool CppCodeParser::matchBaseSpecifier(ClassNode *classe, bool isClass)
+{
+ Node::Access access;
+
+ switch (tok) {
+ case Tok_public:
+ access = Node::Public;
+ readToken();
+ break;
+ case Tok_protected:
+ access = Node::Protected;
+ readToken();
+ break;
+ case Tok_private:
+ access = Node::Private;
+ readToken();
+ break;
+ default:
+ access = isClass ? Node::Private : Node::Public;
+ }
+
+ if (tok == Tok_virtual)
+ readToken();
+
+ CodeChunk baseClass;
+ if (!matchDataType(&baseClass))
+ return false;
+
+ classe->addUnresolvedBaseClass(access, baseClass.toPath(), baseClass.toString());
+ return true;
+}
+
+bool CppCodeParser::matchBaseList(ClassNode *classe, bool isClass)
+{
+ for (;;) {
+ if (!matchBaseSpecifier(classe, isClass))
+ return false;
+ if (tok == Tok_LeftBrace)
+ return true;
+ if (!match(Tok_Comma))
+ return false;
+ }
+}
+
+/*!
+ Parse a C++ class, union, or struct declaration.
+
+ This function only handles one level of class nesting, but that is
+ sufficient for Qt because there are no cases of class nesting more
+ than one level deep.
+ */
+bool CppCodeParser::matchClassDecl(Aggregate *parent,
+ const QString &templateStuff)
+{
+ bool isClass = (tok == Tok_class);
+ readToken();
+
+ bool compat = matchCompat();
+
+ if (tok != Tok_Ident)
+ return false;
+ while (tok == Tok_Ident)
+ readToken();
+ if (tok == Tok_Gulbrandsen) {
+ Node* n = parent->findChildNode(previousLexeme(),Node::Class);
+ if (n) {
+ parent = static_cast<Aggregate*>(n);
+ if (parent) {
+ readToken();
+ if (tok != Tok_Ident)
+ return false;
+ readToken();
+ }
+ }
+ }
+ if (tok != Tok_Colon && tok != Tok_LeftBrace)
+ return false;
+
+ /*
+ So far, so good. We have 'class Foo {' or 'class Foo :'.
+ This is enough to recognize a class definition.
+ */
+ ClassNode *classe = new ClassNode(parent, previousLexeme());
+ classe->setAccess(access);
+ classe->setLocation(location());
+ if (compat)
+ classe->setStatus(Node::Compat);
+ if (!physicalModuleName.isEmpty())
+ classe->setPhysicalModuleName(physicalModuleName);
+ classe->setTemplateStuff(templateStuff);
+
+ if (match(Tok_Colon) && !matchBaseList(classe, isClass))
+ return false;
+ if (!match(Tok_LeftBrace))
+ return false;
+
+ Node::Access outerAccess = access;
+ access = isClass ? Node::Private : Node::Public;
+ FunctionNode::Metaness outerMetaness = metaness_;
+ metaness_ = FunctionNode::Plain;
+
+ bool matches = (matchDeclList(classe) && match(Tok_RightBrace) &&
+ match(Tok_Semicolon));
+ access = outerAccess;
+ metaness_ = outerMetaness;
+ return matches;
+}
+
+bool CppCodeParser::matchNamespaceDecl(Aggregate *parent)
+{
+ readToken(); // skip 'namespace'
+ if (tok != Tok_Ident)
+ return false;
+ while (tok == Tok_Ident)
+ readToken();
+ if (tok != Tok_LeftBrace)
+ return false;
+
+ /*
+ So far, so good. We have 'namespace Foo {'.
+ */
+ QString namespaceName = previousLexeme();
+ NamespaceNode* ns = 0;
+ if (parent)
+ ns = static_cast<NamespaceNode*>(parent->findChildNode(namespaceName, Node::Namespace));
+ if (!ns) {
+ ns = new NamespaceNode(parent, namespaceName);
+ ns->setAccess(access);
+ ns->setLocation(location());
+ }
+
+ readToken(); // skip '{'
+ bool matched = matchDeclList(ns);
+ return matched && match(Tok_RightBrace);
+}
+
+/*!
+ Match a C++ \c using clause. Return \c true if the match
+ is successful. Otherwise false.
+
+ If the \c using clause is for a namespace, an open namespace
+ <is inserted for qdoc to look in to find things.
+
+ If the \c using clause is a base class member function, the
+ member function is added to \a parent as an unresolved
+ \c using clause.
+ */
+bool CppCodeParser::matchUsingDecl(Aggregate* parent)
+{
+ bool usingNamespace = false;
+ readToken(); // skip 'using'
+
+ if (tok == Tok_namespace) {
+ usingNamespace = true;
+ readToken();
+ }
+
+ int openLeftAngles = 0;
+ int openLeftParens = 0;
+ bool usingOperator = false;
+ QString name;
+ while (tok != Tok_Semicolon) {
+ if ((tok != Tok_Ident) && (tok != Tok_Gulbrandsen)) {
+ if (tok == Tok_LeftAngle) {
+ ++openLeftAngles;
+ }
+ else if (tok == Tok_RightAngle) {
+ if (openLeftAngles <= 0)
+ return false;
+ --openLeftAngles;
+ }
+ else if (tok == Tok_Comma) {
+ if (openLeftAngles <= 0)
+ return false;
+ }
+ else if (tok == Tok_operator) {
+ usingOperator = true;
+ }
+ else if (tok == Tok_SomeOperator) {
+ if (!usingOperator)
+ return false;
+ }
+ else if (tok == Tok_LeftParen) {
+ ++openLeftParens;
+ }
+ else if (tok == Tok_RightParen) {
+ if (openLeftParens <= 0)
+ return false;
+ --openLeftParens;
+ }
+ else {
+ return false;
+ }
+ }
+ name += lexeme();
+ readToken();
+ }
+
+ if (usingNamespace) {
+ // 'using namespace Foo;'.
+ qdb_->insertOpenNamespace(name);
+ }
+ else if (parent && parent->isClass()) {
+ ClassNode* cn = static_cast<ClassNode*>(parent);
+ cn->addUnresolvedUsingClause(name);
+ }
+ return true;
+}
+
+bool CppCodeParser::matchEnumItem(Aggregate *parent, EnumNode *enume)
+{
+ if (!match(Tok_Ident))
+ return false;
+
+ QString name = previousLexeme();
+ CodeChunk val;
+ int parenLevel = 0;
+
+ if (match(Tok_Equal)) {
+ while (tok != Tok_RightBrace && tok != Tok_Eoi) {
+ if (tok == Tok_LeftParen)
+ parenLevel++;
+ else if (tok == Tok_RightParen)
+ parenLevel--;
+ else if (tok == Tok_Comma) {
+ if (parenLevel <= 0)
+ break;
+ }
+ val.append(lexeme());
+ readToken();
+ }
+ }
+
+ if (enume) {
+ QString strVal = val.toString();
+ if (strVal.isEmpty()) {
+ if (enume->items().isEmpty()) {
+ strVal = "0";
+ }
+ else {
+ QString last = enume->items().last().value();
+ bool ok;
+ int n = last.toInt(&ok);
+ if (ok) {
+ if (last.startsWith(QLatin1Char('0')) && last.size() > 1) {
+ if (last.startsWith("0x") || last.startsWith("0X"))
+ strVal = last.left(2) + QString::number(n + 1, 16);
+ else
+ strVal = QLatin1Char('0') + QString::number(n + 1, 8);
+ }
+ else
+ strVal = QString::number(n + 1);
+ }
+ }
+ }
+
+ enume->addItem(EnumItem(name, strVal));
+ }
+ else {
+ VariableNode *var = new VariableNode(parent, name);
+ var->setAccess(access);
+ var->setLocation(location());
+ var->setLeftType("const int");
+ var->setStatic(true);
+ }
+ return true;
+}
+
+bool CppCodeParser::matchEnumDecl(Aggregate *parent)
+{
+ QString name;
+
+ if (!match(Tok_enum))
+ return false;
+ if (match(Tok_Ident))
+ name = previousLexeme();
+ if (tok != Tok_LeftBrace)
+ return false;
+
+ EnumNode *enume = 0;
+
+ if (!name.isEmpty()) {
+ enume = new EnumNode(parent, name);
+ enume->setAccess(access);
+ enume->setLocation(location());
+ }
+
+ readToken();
+
+ if (!matchEnumItem(parent, enume))
+ return false;
+
+ while (match(Tok_Comma)) {
+ if (!matchEnumItem(parent, enume))
+ return false;
+ }
+ return match(Tok_RightBrace) && match(Tok_Semicolon);
+}
+
+bool CppCodeParser::matchTypedefDecl(Aggregate *parent)
+{
+ CodeChunk dataType;
+ QString name;
+
+ if (!match(Tok_typedef))
+ return false;
+ if (!matchDataType(&dataType, &name))
+ return false;
+ if (!match(Tok_Semicolon))
+ return false;
+
+ if (parent && !parent->findChildNode(name, Node::Typedef)) {
+ TypedefNode* td = new TypedefNode(parent, name);
+ td->setAccess(access);
+ td->setLocation(location());
+ }
+ return true;
+}
+
+bool CppCodeParser::matchProperty(Aggregate *parent)
+{
+ int expected_tok = Tok_LeftParen;
+ if (match(Tok_Q_PRIVATE_PROPERTY)) {
+ expected_tok = Tok_Comma;
+ if (!skipTo(Tok_Comma))
+ return false;
+ }
+ else if (!match(Tok_Q_PROPERTY) &&
+ !match(Tok_Q_OVERRIDE) &&
+ !match(Tok_QDOC_PROPERTY)) {
+ return false;
+ }
+
+ if (!match(expected_tok))
+ return false;
+
+ QString name;
+ CodeChunk dataType;
+ if (!matchDataType(&dataType, &name))
+ return false;
+
+ PropertyNode *property = new PropertyNode(parent, name);
+ property->setAccess(Node::Public);
+ property->setLocation(location());
+ property->setDataType(dataType.toString());
+
+ while (tok != Tok_RightParen && tok != Tok_Eoi) {
+ if (!match(Tok_Ident))
+ return false;
+ QString key = previousLexeme();
+ QString value;
+
+ // Keywords with no associated values
+ if (key == "CONSTANT") {
+ property->setConstant();
+ continue;
+ }
+ else if (key == "FINAL") {
+ property->setFinal();
+ continue;
+ }
+
+ if (match(Tok_Ident) || match(Tok_Number)) {
+ value = previousLexeme();
+ }
+ else if (match(Tok_LeftParen)) {
+ int depth = 1;
+ while (tok != Tok_Eoi) {
+ if (tok == Tok_LeftParen) {
+ readToken();
+ ++depth;
+ } else if (tok == Tok_RightParen) {
+ readToken();
+ if (--depth == 0)
+ break;
+ } else {
+ readToken();
+ }
+ }
+ value = "?";
+ }
+
+ if (key == "READ")
+ qdb_->addPropertyFunction(property, value, PropertyNode::Getter);
+ else if (key == "WRITE") {
+ qdb_->addPropertyFunction(property, value, PropertyNode::Setter);
+ property->setWritable(true);
+ }
+ else if (key == "STORED")
+ property->setStored(value.toLower() == "true");
+ else if (key == "DESIGNABLE") {
+ QString v = value.toLower();
+ if (v == "true")
+ property->setDesignable(true);
+ else if (v == "false")
+ property->setDesignable(false);
+ else {
+ property->setDesignable(false);
+ property->setRuntimeDesFunc(value);
+ }
+ }
+ else if (key == "RESET")
+ qdb_->addPropertyFunction(property, value, PropertyNode::Resetter);
+ else if (key == "NOTIFY") {
+ qdb_->addPropertyFunction(property, value, PropertyNode::Notifier);
+ } else if (key == "REVISION") {
+ int revision;
+ bool ok;
+ revision = value.toInt(&ok);
+ if (ok)
+ property->setRevision(revision);
+ else
+ location().warning(tr("Invalid revision number: %1").arg(value));
+ } else if (key == "SCRIPTABLE") {
+ QString v = value.toLower();
+ if (v == "true")
+ property->setScriptable(true);
+ else if (v == "false")
+ property->setScriptable(false);
+ else {
+ property->setScriptable(false);
+ property->setRuntimeScrFunc(value);
+ }
+ }
+ }
+ match(Tok_RightParen);
+ return true;
+}
+
+/*!
+ Parse a C++ declaration.
+ */
+bool CppCodeParser::matchDeclList(Aggregate *parent)
+{
+ ExtraFuncData extra;
+ QString templateStuff;
+ int braceDepth0 = tokenizer->braceDepth();
+ if (tok == Tok_RightBrace) // prevents failure on empty body
+ braceDepth0++;
+
+ while (tokenizer->braceDepth() >= braceDepth0 && tok != Tok_Eoi) {
+ switch (tok) {
+ case Tok_Colon:
+ readToken();
+ break;
+ case Tok_class:
+ case Tok_struct:
+ case Tok_union:
+ matchClassDecl(parent, templateStuff);
+ break;
+ case Tok_namespace:
+ matchNamespaceDecl(parent);
+ break;
+ case Tok_using:
+ matchUsingDecl(parent);
+ break;
+ case Tok_template:
+ {
+ CodeChunk dataType;
+ readToken();
+ matchTemplateAngles(&dataType);
+ templateStuff = dataType.toString();
+ }
+ continue;
+ case Tok_enum:
+ matchEnumDecl(parent);
+ break;
+ case Tok_typedef:
+ matchTypedefDecl(parent);
+ break;
+ case Tok_private:
+ readToken();
+ access = Node::Private;
+ metaness_ = FunctionNode::Plain;
+ break;
+ case Tok_protected:
+ readToken();
+ access = Node::Protected;
+ metaness_ = FunctionNode::Plain;
+ break;
+ case Tok_public:
+ readToken();
+ access = Node::Public;
+ metaness_ = FunctionNode::Plain;
+ break;
+ case Tok_signals:
+ case Tok_Q_SIGNALS:
+ readToken();
+ access = Node::Public;
+ metaness_ = FunctionNode::Signal;
+ break;
+ case Tok_slots:
+ case Tok_Q_SLOTS:
+ readToken();
+ metaness_ = FunctionNode::Slot;
+ break;
+ case Tok_Q_OBJECT:
+ readToken();
+ break;
+ case Tok_Q_OVERRIDE:
+ case Tok_Q_PROPERTY:
+ case Tok_Q_PRIVATE_PROPERTY:
+ case Tok_QDOC_PROPERTY:
+ if (!matchProperty(parent)) {
+ location().warning(tr("Failed to parse token %1 in property declaration").arg(lexeme()));
+ skipTo(Tok_RightParen);
+ match(Tok_RightParen);
+ }
+ break;
+ case Tok_Q_DECLARE_SEQUENTIAL_ITERATOR:
+ readToken();
+ if (match(Tok_LeftParen) && match(Tok_Ident))
+ sequentialIteratorClasses.insert(previousLexeme(), location().fileName());
+ match(Tok_RightParen);
+ break;
+ case Tok_Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR:
+ readToken();
+ if (match(Tok_LeftParen) && match(Tok_Ident))
+ mutableSequentialIteratorClasses.insert(previousLexeme(), location().fileName());
+ match(Tok_RightParen);
+ break;
+ case Tok_Q_DECLARE_ASSOCIATIVE_ITERATOR:
+ readToken();
+ if (match(Tok_LeftParen) && match(Tok_Ident))
+ associativeIteratorClasses.insert(previousLexeme(), location().fileName());
+ match(Tok_RightParen);
+ break;
+ case Tok_Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR:
+ readToken();
+ if (match(Tok_LeftParen) && match(Tok_Ident))
+ mutableAssociativeIteratorClasses.insert(previousLexeme(), location().fileName());
+ match(Tok_RightParen);
+ break;
+ case Tok_Q_DECLARE_FLAGS:
+ readToken();
+ if (match(Tok_LeftParen) && match(Tok_Ident)) {
+ QString flagsType = previousLexeme();
+ if (match(Tok_Comma) && match(Tok_Ident)) {
+ QString name = previousLexeme();
+ TypedefNode *flagsNode = new TypedefNode(parent, flagsType);
+ flagsNode->setAccess(access);
+ flagsNode->setLocation(location());
+ EnumNode* en = static_cast<EnumNode*>(parent->findChildNode(name, Node::Enum));
+ if (en)
+ en->setFlagsType(flagsNode);
+ }
+ }
+ match(Tok_RightParen);
+ break;
+ case Tok_QT_MODULE:
+ readToken();
+ if (match(Tok_LeftParen) && match(Tok_Ident))
+ physicalModuleName = previousLexeme();
+ if (!physicalModuleName.startsWith("Qt"))
+ physicalModuleName.prepend("Qt");
+ match(Tok_RightParen);
+ break;
+ default:
+ if (!matchFunctionDecl(parent, 0, 0, templateStuff, extra)) {
+ while (tok != Tok_Eoi &&
+ (tokenizer->braceDepth() > braceDepth0 ||
+ (!match(Tok_Semicolon) &&
+ tok != Tok_public && tok != Tok_protected &&
+ tok != Tok_private))) {
+ readToken();
+ }
+ }
+ }
+ templateStuff.clear();
+ }
+ return true;
+}
+
+/*!
+ This is called by parseSourceFile() to do the actual parsing
+ and tree building.
+ */
+bool CppCodeParser::matchDocsAndStuff()
+{
+ ExtraFuncData extra;
+ const QSet<QString>& topicCommandsAllowed = topicCommands();
+ const QSet<QString>& otherMetacommandsAllowed = otherMetaCommands();
+ const QSet<QString>& metacommandsAllowed = topicCommandsAllowed + otherMetacommandsAllowed;
+
+ while (tok != Tok_Eoi) {
+ if (tok == Tok_Doc) {
+ /*
+ lexeme() returns an entire qdoc comment.
+ */
+ QString comment = lexeme();
+ Location start_loc(location());
+ readToken();
+
+ Doc::trimCStyleComment(start_loc,comment);
+ Location end_loc(location());
+
+ /*
+ Doc parses the comment.
+ */
+ Doc doc(start_loc,end_loc,comment,metacommandsAllowed, topicCommandsAllowed);
+ QString topic;
+ bool isQmlPropertyTopic = false;
+ bool isJsPropertyTopic = false;
+
+ const TopicList& topics = doc.topicsUsed();
+ if (!topics.isEmpty()) {
+ topic = topics[0].topic;
+ if ((topic == COMMAND_QMLPROPERTY) ||
+ (topic == COMMAND_QMLPROPERTYGROUP) ||
+ (topic == COMMAND_QMLATTACHEDPROPERTY)) {
+ isQmlPropertyTopic = true;
+ }
+ else if ((topic == COMMAND_JSPROPERTY) ||
+ (topic == COMMAND_JSPROPERTYGROUP) ||
+ (topic == COMMAND_JSATTACHEDPROPERTY)) {
+ isJsPropertyTopic = true;
+ }
+ }
+ NodeList nodes;
+ DocList docs;
+
+ if (topic.isEmpty()) {
+ QStringList parentPath;
+ FunctionNode *clone;
+ FunctionNode *func = 0;
+
+ if (matchFunctionDecl(0, &parentPath, &clone, QString(), extra)) {
+ func = qdb_->findFunctionNode(parentPath, clone);
+ /*
+ If the node was not found, then search for it in the
+ open C++ namespaces. We don't expect this search to
+ be necessary often. Nor do we expect it to succeed
+ very often.
+ */
+ if (func == 0)
+ func = qdb_->findNodeInOpenNamespace(parentPath, clone);
+
+ if (func) {
+ func->borrowParameterNames(clone);
+ nodes.append(func);
+ docs.append(doc);
+ }
+ delete clone;
+ }
+ else {
+ doc.location().warning(tr("Cannot tie this documentation to anything"),
+ tr("I found a /*! ... */ comment, but there was no "
+ "topic command (e.g., '\\%1', '\\%2') in the "
+ "comment and no function definition following "
+ "the comment.")
+ .arg(COMMAND_FN).arg(COMMAND_PAGE));
+ }
+ }
+ else if (isQmlPropertyTopic || isJsPropertyTopic) {
+ Doc nodeDoc = doc;
+ processQmlProperties(nodeDoc, nodes, docs, isJsPropertyTopic);
+ }
+ else {
+ ArgList args;
+ const QSet<QString>& topicCommandsUsed = topicCommandsAllowed & doc.metaCommandsUsed();
+ if (topicCommandsUsed.count() > 0) {
+ topic = *topicCommandsUsed.constBegin();
+ args = doc.metaCommandArgs(topic);
+ }
+ if (topicCommandsUsed.count() > 1) {
+ QString topics;
+ QSet<QString>::ConstIterator t = topicCommandsUsed.constBegin();
+ while (t != topicCommandsUsed.constEnd()) {
+ topics += " \\" + *t + QLatin1Char(',');
+ ++t;
+ }
+ topics[topics.lastIndexOf(',')] = '.';
+ int i = topics.lastIndexOf(',');
+ topics[i] = ' ';
+ topics.insert(i+1,"and");
+ doc.location().warning(tr("Multiple topic commands found in comment: %1").arg(topics));
+ }
+ ArgList::ConstIterator a = args.constBegin();
+ while (a != args.constEnd()) {
+ Doc nodeDoc = doc;
+ Node *node = processTopicCommand(nodeDoc,topic,*a);
+ if (node != 0) {
+ nodes.append(node);
+ docs.append(nodeDoc);
+ }
+ ++a;
+ }
+ }
+
+ NodeList::Iterator n = nodes.begin();
+ QList<Doc>::Iterator d = docs.begin();
+ while (n != nodes.end()) {
+ processOtherMetaCommands(*d, *n);
+ (*n)->setDoc(*d);
+ checkModuleInclusion(*n);
+ if ((*n)->isAggregate() && ((Aggregate *)*n)->includes().isEmpty()) {
+ Aggregate *m = static_cast<Aggregate *>(*n);
+ while (m->parent() && m->physicalModuleName().isEmpty()) {
+ m = m->parent();
+ }
+ if (m == *n)
+ ((Aggregate *)*n)->addInclude((*n)->name());
+ else
+ ((Aggregate *)*n)->setIncludes(m->includes());
+ }
+ ++d;
+ ++n;
+ }
+ }
+ else if (tok == Tok_using) {
+ matchUsingDecl(0);
+ }
+ else {
+ QStringList parentPath;
+ FunctionNode *clone;
+ FunctionNode *node = 0;
+
+ if (matchFunctionDecl(0, &parentPath, &clone, QString(), extra)) {
+ /*
+ The location of the definition is more interesting
+ than that of the declaration. People equipped with
+ a sophisticated text editor can respond to warnings
+ concerning undocumented functions very quickly.
+
+ Signals are implemented in uninteresting files
+ generated by moc.
+ */
+ node = qdb_->findFunctionNode(parentPath, clone);
+ if (node != 0 && node->metaness() != FunctionNode::Signal)
+ node->setLocation(clone->location());
+ delete clone;
+ }
+ else {
+ if (tok != Tok_Doc)
+ readToken();
+ }
+ }
+ }
+ return true;
+}
+
+/*!
+ This function uses a Tokenizer to parse the function \a signature
+ in an attempt to match it to the signature of a child node of \a root.
+ If a match is found, \a funcPtr is set to point to the matching node
+ and true is returned.
+ */
+bool CppCodeParser::makeFunctionNode(const QString& signature,
+ QStringList* parentPathPtr,
+ FunctionNode** funcPtr,
+ ExtraFuncData& extra)
+{
+ Tokenizer* outerTokenizer = tokenizer;
+ int outerTok = tok;
+
+ QByteArray latin1 = signature.toLatin1();
+ Tokenizer stringTokenizer(location(), latin1);
+ stringTokenizer.setParsingFnOrMacro(true);
+ tokenizer = &stringTokenizer;
+ readToken();
+
+ inMacroCommand_ = extra.isMacro;
+ bool ok = matchFunctionDecl(extra.root, parentPathPtr, funcPtr, QString(), extra);
+ inMacroCommand_ = false;
+ // potential memory leak with funcPtr
+
+ tokenizer = outerTokenizer;
+ tok = outerTok;
+ return ok;
+}
+
+/*!
+ This function uses a Tokenizer to parse the \a parameters of a
+ function into the parameter vector \a {pvect}.
+ */
+bool CppCodeParser::parseParameters(const QString& parameters,
+ QVector<Parameter>& pvect,
+ bool& isQPrivateSignal)
+{
+ Tokenizer* outerTokenizer = tokenizer;
+ int outerTok = tok;
+
+ QByteArray latin1 = parameters.toLatin1();
+ Tokenizer stringTokenizer(Location(), latin1);
+ stringTokenizer.setParsingFnOrMacro(true);
+ tokenizer = &stringTokenizer;
+ readToken();
+
+ inMacroCommand_ = false;
+ do {
+ if (!matchParameter(pvect, isQPrivateSignal))
+ return false;
+ } while (match(Tok_Comma));
+
+ tokenizer = outerTokenizer;
+ tok = outerTok;
+ return true;
+}
+
+/*!
+ Create a new FunctionNode for a QML method or signal, as
+ specified by \a type, as a child of \a parent. \a sig is
+ the complete signature, and if \a attached is true, the
+ method or signal is "attached". \a qdoctag is the text of
+ the \a type.
+
+ \a parent is the QML class node. The QML module and QML
+ type names have already been consumed to find \a parent.
+ What remains in \a sig is the method signature. The method
+ must be a child of \a parent.
+ */
+FunctionNode* CppCodeParser::makeFunctionNode(const Doc& doc,
+ const QString& sig,
+ Aggregate* parent,
+ Node::NodeType type,
+ bool attached,
+ QString qdoctag)
+{
+ QStringList pp;
+ FunctionNode* fn = 0;
+ ExtraFuncData extra(parent, type, attached);
+ if (!makeFunctionNode(sig, &pp, &fn, extra) && !makeFunctionNode("void " + sig, &pp, &fn, extra)) {
+ doc.location().warning(tr("Invalid syntax in '\\%1'").arg(qdoctag));
+ }
+ return fn;
+}
+
+void CppCodeParser::parseQiteratorDotH(const Location &location, const QString &filePath)
+{
+ QFile file(filePath);
+ if (!file.open(QFile::ReadOnly))
+ return;
+
+ QString text = file.readAll();
+ text.remove("\r");
+ text.remove("\\\n");
+ QStringList lines = text.split(QLatin1Char('\n'));
+ lines = lines.filter("Q_DECLARE");
+ lines.replaceInStrings(QRegExp("#define Q[A-Z_]*\\(C\\)"), QString());
+
+ if (lines.size() == 4) {
+ sequentialIteratorDefinition = lines[0];
+ mutableSequentialIteratorDefinition = lines[1];
+ associativeIteratorDefinition = lines[2];
+ mutableAssociativeIteratorDefinition = lines[3];
+ }
+ else {
+ location.warning(tr("The qiterator.h hack failed"));
+ }
+}
+
+void CppCodeParser::instantiateIteratorMacro(const QString &container,
+ const QString &includeFile,
+ const QString &macroDef)
+{
+ QString resultingCode = macroDef;
+ resultingCode.replace(QRegExp("\\bC\\b"), container);
+ resultingCode.remove(QRegExp("\\s*##\\s*"));
+
+ Location loc(includeFile); // hack to get the include file for free
+ QByteArray latin1 = resultingCode.toLatin1();
+ Tokenizer stringTokenizer(loc, latin1);
+ tokenizer = &stringTokenizer;
+ readToken();
+ matchDeclList(QDocDatabase::qdocDB()->primaryTreeRoot());
+}
+
+void CppCodeParser::createExampleFileNodes(DocumentNode *dn)
+{
+ QString examplePath = dn->name();
+ QString proFileName = examplePath + QLatin1Char('/') + examplePath.split(QLatin1Char('/')).last() + ".pro";
+ QString userFriendlyFilePath;
+
+ QString fullPath = Config::findFile(dn->doc().location(),
+ exampleFiles,
+ exampleDirs,
+ proFileName,
+ userFriendlyFilePath);
+
+ if (fullPath.isEmpty()) {
+ QString tmp = proFileName;
+ proFileName = examplePath + QLatin1Char('/') + "qbuild.pro";
+ userFriendlyFilePath.clear();
+ fullPath = Config::findFile(dn->doc().location(),
+ exampleFiles,
+ exampleDirs,
+ proFileName,
+ userFriendlyFilePath);
+ if (fullPath.isEmpty()) {
+ proFileName = examplePath + QLatin1Char('/') + examplePath.split(QLatin1Char('/')).last() + ".qmlproject";
+ userFriendlyFilePath.clear();
+ fullPath = Config::findFile(dn->doc().location(),
+ exampleFiles,
+ exampleDirs,
+ proFileName,
+ userFriendlyFilePath);
+ if (fullPath.isEmpty()) {
+ QString details = QLatin1String("Example directories: ") + exampleDirs.join(QLatin1Char(' '));
+ if (!exampleFiles.isEmpty())
+ details += QLatin1String(", example files: ") + exampleFiles.join(QLatin1Char(' '));
+ dn->location().warning(tr("Cannot find file '%1' or '%2'").arg(tmp).arg(proFileName), details);
+ dn->location().warning(tr(" EXAMPLE PATH DOES NOT EXIST: %1").arg(examplePath), details);
+ return;
+ }
+ }
+ }
+
+ int sizeOfBoringPartOfName = fullPath.size() - proFileName.size();
+ if (fullPath.startsWith("./"))
+ sizeOfBoringPartOfName = sizeOfBoringPartOfName - 2;
+ fullPath.truncate(fullPath.lastIndexOf('/'));
+
+ QStringList exampleFiles = Config::getFilesHere(fullPath,exampleNameFilter);
+ QString imagesPath = fullPath + "/images";
+ QStringList imageFiles = Config::getFilesHere(imagesPath,exampleImageFilter);
+ if (!exampleFiles.isEmpty()) {
+ // move main.cpp and to the end, if it exists
+ QString mainCpp;
+ QMutableStringListIterator i(exampleFiles);
+ i.toBack();
+ while (i.hasPrevious()) {
+ QString fileName = i.previous();
+ if (fileName.endsWith("/main.cpp")) {
+ mainCpp = fileName;
+ i.remove();
+ }
+ else if (fileName.contains("/qrc_") || fileName.contains("/moc_")
+ || fileName.contains("/ui_"))
+ i.remove();
+ }
+ if (!mainCpp.isEmpty())
+ exampleFiles.append(mainCpp);
+
+ // add any qmake Qt resource files and qmake project files
+ exampleFiles += Config::getFilesHere(fullPath, "*.qrc *.pro *.qmlproject qmldir");
+ }
+
+ foreach (const QString &exampleFile, exampleFiles) {
+ new DocumentNode(dn,
+ exampleFile.mid(sizeOfBoringPartOfName),
+ Node::File,
+ Node::NoPageType);
+ }
+ foreach (const QString &imageFile, imageFiles) {
+ new DocumentNode(dn,
+ imageFile.mid(sizeOfBoringPartOfName),
+ Node::Image,
+ Node::NoPageType);
+ }
+}
+
+QT_END_NAMESPACE