tools/qdoc3/cppcodeparser.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/qdoc3/cppcodeparser.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,2197 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** 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 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*
+  cppcodeparser.cpp
+*/
+
+#include <QtCore>
+#include <qfile.h>
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "codechunk.h"
+#include "config.h"
+#include "cppcodeparser.h"
+#include "tokenizer.h"
+#include "tree.h"
+
+QT_BEGIN_NAMESPACE
+
+/* qmake ignore Q_OBJECT */
+
+#define COMMAND_CLASS                   Doc::alias("class")
+#define COMMAND_CONTENTSPAGE            Doc::alias("contentspage")
+#define COMMAND_ENUM                    Doc::alias("enum")
+#define COMMAND_EXAMPLE                 Doc::alias("example")
+#define COMMAND_EXTERNALPAGE            Doc::alias("externalpage")
+#define COMMAND_FILE                    Doc::alias("file") // ### don't document
+#define COMMAND_FN                      Doc::alias("fn")
+#define COMMAND_GROUP                   Doc::alias("group")
+#define COMMAND_HEADERFILE              Doc::alias("headerfile")
+#define COMMAND_INDEXPAGE               Doc::alias("indexpage")
+#define COMMAND_INHEADERFILE            Doc::alias("inheaderfile") // ### don't document
+#define COMMAND_MACRO                   Doc::alias("macro")
+#define COMMAND_MODULE                  Doc::alias("module") // ### don't document
+#define COMMAND_NAMESPACE               Doc::alias("namespace")
+#define COMMAND_OVERLOAD                Doc::alias("overload")
+#define COMMAND_NEXTPAGE                Doc::alias("nextpage")
+#define COMMAND_PAGE                    Doc::alias("page")
+#define COMMAND_PREVIOUSPAGE            Doc::alias("previouspage")
+#define COMMAND_PROPERTY                Doc::alias("property")
+#define COMMAND_REIMP                   Doc::alias("reimp")
+#define COMMAND_RELATES                 Doc::alias("relates")
+#define COMMAND_SERVICE                 Doc::alias("service")
+#define COMMAND_STARTPAGE               Doc::alias("startpage")
+#define COMMAND_TYPEDEF                 Doc::alias("typedef")
+#define COMMAND_VARIABLE                Doc::alias("variable")
+
+#ifdef QDOC_QML
+#define COMMAND_QMLCLASS                Doc::alias("qmlclass")
+#define COMMAND_QMLPROPERTY             Doc::alias("qmlproperty")
+#define COMMAND_QMLATTACHEDPROPERTY     Doc::alias("qmlattachedproperty")
+#define COMMAND_QMLINHERITS             Doc::alias("inherits")
+#define COMMAND_QMLSIGNAL               Doc::alias("qmlsignal")
+#define COMMAND_QMLMETHOD               Doc::alias("qmlmethod")
+#define COMMAND_QMLDEFAULT              Doc::alias("default")
+#endif
+
+QStringList CppCodeParser::exampleFiles;
+QStringList CppCodeParser::exampleDirs;
+
+static void extractPageLinkAndDesc(const QString &arg,
+                                   QString *link,
+                                   QString *desc)
+{
+    QRegExp bracedRegExp("\\{([^{}]*)\\}(?:\\{([^{}]*)\\})?");
+
+    if (bracedRegExp.exactMatch(arg)) {
+        *link = bracedRegExp.cap(1);
+        *desc = bracedRegExp.cap(2);
+        if (desc->isEmpty())
+            *desc = *link;
+    }
+    else {
+        int spaceAt = arg.indexOf(" ");
+        if (arg.contains(".html") && spaceAt != -1) {
+            *link = arg.left(spaceAt).trimmed();
+            *desc = arg.mid(spaceAt).trimmed();
+        }
+        else {
+            *link = arg;
+            *desc = arg;
+        }
+    }
+}
+
+static void setLink(Node *node, Node::LinkType linkType, const QString &arg)
+{
+    QString link;
+    QString desc;
+    extractPageLinkAndDesc(arg, &link, &desc);
+    node->setLink(linkType, link, desc);
+}
+
+/*
+    This is used for fuzzy matching only, which in turn is only used
+    for Qt Jambi.
+*/
+static QString cleanType(const QString &type, const Tree *tree)
+{
+    QString result = type;
+    result.replace("qlonglong", "long long");
+    result.replace("qulonglong", "unsigned long long");
+    result.replace("qreal", "double");
+    result.replace(QRegExp("\\bu(int|short|char|long)\\b"), "unsigned \\1");
+    result.replace("QRgb", "unsigned int");
+    result.replace(" >", ">");
+    result.remove(" const[]");
+    result.replace("QStringList<QString>", "QStringList");
+    result.replace("qint8", "char");
+    result.replace("qint16", "short");
+    result.replace("qint32", "int");
+    result.replace("qint64", "long long");
+    result.replace("quint8", "unsigned char");
+    result.replace("quint16", "unsigned short");
+    result.replace("quint32", "unsigned int");
+    result.replace("quint64", "unsigned long long");
+
+    if (result.contains("QFlags")) {
+        QRegExp regExp("QFlags<(((?:[^<>]+::)*)([^<>:]+))>");
+        int pos = 0;
+        while ((pos = result.indexOf(regExp, pos)) != -1) {
+            // we assume that the path for the associated enum
+            // is the same as for the flag typedef
+            QStringList path = regExp.cap(2).split("::",
+                                                   QString::SkipEmptyParts);
+            const EnumNode *enume = static_cast<const EnumNode *>(
+                             tree->findNode(QStringList(path) << regExp.cap(3),
+                                            Node::Enum));
+            if (enume && enume->flagsType())
+                result.replace(pos, regExp.matchedLength(),
+                 (QStringList(path) << enume->flagsType()->name()).join("::"));
+            ++pos;
+        }
+    }
+    if (result.contains("::")) {
+        // remove needless (and needful) class prefixes
+        QRegExp regExp("[A-Za-z0-9_]+::");
+        result.replace(regExp, "");
+    }
+    return result;
+}
+
+/*!
+  The constructor initializes some regular expressions
+  and calls reset().
+ */
+CppCodeParser::CppCodeParser()
+    : varComment("/\\*\\s*([a-zA-Z_0-9]+)\\s*\\*/"), sep("(?:<[^>]+>)?::")
+{
+    reset(0);
+}
+
+/*!
+  The destructor is trivial.
+ */
+CppCodeParser::~CppCodeParser()
+{
+}
+
+void CppCodeParser::initializeParser(const Config &config)
+{
+    CodeParser::initializeParser(config);
+
+    nodeTypeMap.insert(COMMAND_NAMESPACE, Node::Namespace);
+    nodeTypeMap.insert(COMMAND_CLASS, Node::Class);
+    nodeTypeMap.insert(COMMAND_SERVICE, 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.getStringList(CONFIG_EXAMPLES);
+    exampleDirs = config.getStringList(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 *.ui";
+}
+
+void CppCodeParser::terminateParser()
+{
+    nodeTypeMap.clear();
+    CodeParser::terminateParser();
+}
+
+QString CppCodeParser::language()
+{
+    return "Cpp";
+}
+
+QString CppCodeParser::headerFileNameFilter()
+{
+    return "*.ch *.h *.h++ *.hh *.hpp *.hxx";
+}
+
+QString CppCodeParser::sourceFileNameFilter()
+{
+    return "*.c++ *.cc *.cpp *.cxx";
+}
+
+/*!
+  Parse the C++ header file identified by \a filePath
+  and add the parsed contents to the big \a tree. The
+  \a location is used for reporting errors.
+ */
+void CppCodeParser::parseHeaderFile(const Location& location,
+                                    const QString& filePath,
+                                    Tree *tree)
+{
+    FILE *in = fopen(QFile::encodeName(filePath), "r");
+    if (!in) {
+        location.error(tr("Cannot open C++ header file '%1'").arg(filePath));
+        return;
+    }
+
+    reset(tree);
+    Location fileLocation(filePath);
+    Tokenizer fileTokenizer(fileLocation, in);
+    tokenizer = &fileTokenizer;
+    readToken();
+    matchDeclList(tree->root());
+    if (!fileTokenizer.version().isEmpty())
+        tree->setVersion(fileTokenizer.version());
+    fclose(in);
+
+    if (fileLocation.fileName() == "qiterator.h")
+        parseQiteratorDotH(location, filePath);
+}
+
+/*!
+  Get ready to parse the C++ cpp file identified by \a filePath
+  and add its parsed contents to the big \a tree. \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,
+                                    Tree *tree)
+{
+    FILE *in = fopen(QFile::encodeName(filePath), "r");
+    if (!in) {
+        location.error(tr("Cannot open C++ source file '%1' (%2)").arg(filePath).arg(strerror(errno)));
+        return;
+    }
+
+    reset(tree);
+    Location fileLocation(filePath);
+    Tokenizer fileTokenizer(fileLocation, in);
+    tokenizer = &fileTokenizer;
+    readToken();
+    usedNamespaces.clear();
+    matchDocsAndStuff();
+    fclose(in);
+}
+
+void CppCodeParser::doneParsingHeaderFiles(Tree *tree)
+{
+    tree->resolveInheritance();
+
+    QMapIterator<QString, QString> i(sequentialIteratorClasses);
+    while (i.hasNext()) {
+        i.next();
+        instantiateIteratorMacro(i.key(),
+                                 i.value(),
+                                 sequentialIteratorDefinition,
+                                 tree);
+    }
+    i = mutableSequentialIteratorClasses;
+    while (i.hasNext()) {
+        i.next();
+        instantiateIteratorMacro(i.key(),
+                                 i.value(),
+                                 mutableSequentialIteratorDefinition,
+                                 tree);
+    }
+    i = associativeIteratorClasses;
+    while (i.hasNext()) {
+        i.next();
+        instantiateIteratorMacro(i.key(),
+                                 i.value(),
+                                 associativeIteratorDefinition,
+                                 tree);
+    }
+    i = mutableAssociativeIteratorClasses;
+    while (i.hasNext()) {
+        i.next();
+        instantiateIteratorMacro(i.key(),
+                                 i.value(),
+                                 mutableAssociativeIteratorDefinition,
+                                 tree);
+    }
+    sequentialIteratorDefinition.clear();
+    mutableSequentialIteratorDefinition.clear();
+    associativeIteratorDefinition.clear();
+    mutableAssociativeIteratorDefinition.clear();
+    sequentialIteratorClasses.clear();
+    mutableSequentialIteratorClasses.clear();
+    associativeIteratorClasses.clear();
+    mutableAssociativeIteratorClasses.clear();
+}
+
+void CppCodeParser::doneParsingSourceFiles(Tree *tree)
+{
+    tree->root()->makeUndocumentedChildrenInternal();
+    tree->root()->normalizeOverloads();
+    tree->fixInheritance();
+    tree->resolveProperties();
+}
+
+const FunctionNode *CppCodeParser::findFunctionNode(const QString& synopsis,
+                                                    Tree *tree,
+                                                    Node *relative,
+                                                    bool fuzzy)
+{
+    QStringList parentPath;
+    FunctionNode *clone;
+    FunctionNode *func = 0;
+    int flags = fuzzy ? int(Tree::SearchBaseClasses) : 0;
+
+    reset(tree);
+    if (makeFunctionNode(synopsis, &parentPath, &clone)) {
+        func = tree->findFunctionNode(parentPath, clone, relative, flags);
+
+        /*
+            This is necessary because Roberto's parser resolves typedefs.
+        */
+        if (!func && fuzzy) {
+            func = tre->findFunctionNode(parentPath +
+                                         QStringList(clone->name()),
+                                         relative,
+                                         flags);
+            if (!func && clone->name().contains('_')) {
+                QStringList path = parentPath;
+                path << clone->name().split('_');
+                func = tre->findFunctionNode(path, relative, flags);
+            }
+
+            if (func) {
+                NodeList overloads = func->parent()->overloads(func->name());
+                NodeList candidates;
+                for (int i = 0; i < overloads.count(); ++i) {
+                    FunctionNode *overload = static_cast<FunctionNode *>(overloads.at(i));
+                    if (overload->status() != Node::Compat
+                            && overload->parameters().count() == clone->parameters().count()
+                            && !overload->isConst() == !clone->isConst())
+                        candidates << overload;
+                }
+                if (candidates.count() == 0)
+                    return 0;
+
+                /*
+                    There's only one function with the correct number
+                    of parameters. That must be the one.
+                */
+                if (candidates.count() == 1)
+                    return static_cast<FunctionNode *>(candidates.first());
+
+                overloads = candidates;
+                candidates.clear();
+                for (int i = 0; i < overloads.count(); ++i) {
+                    FunctionNode *overload = static_cast<FunctionNode *>(overloads.at(i));
+                    QList<Parameter> params1 = overload->parameters();
+                    QList<Parameter> params2 = clone->parameters();
+
+                    int j;
+                    for (j = 0; j < params1.count(); ++j) {
+                        if (!params2.at(j).name().startsWith(params1.at(j).name()))
+                            break;
+                    }
+                    if (j == params1.count())
+                        candidates << overload;
+                }
+
+                /*
+                    There are several functions with the correct
+                    parameter count, but only one has the correct
+                    parameter names.
+                */
+                if (candidates.count() == 1)
+                    return static_cast<FunctionNode *>(candidates.first());
+
+                candidates.clear();
+                for (int i = 0; i < overloads.count(); ++i) {
+                    FunctionNode *overload = static_cast<FunctionNode *>(overloads.at(i));
+                    QList<Parameter> params1 = overload->parameters();
+                    QList<Parameter> params2 = clone->parameters();
+
+                    int j;
+                    for (j = 0; j < params1.count(); ++j) {
+                        if (params1.at(j).rightType() != params2.at(j).rightType())
+                            break;
+
+                        if (cleanType(params1.at(j).leftType(), tree)
+                                != cleanType(params2.at(j).leftType(), tree))
+                            break;
+                    }
+                    if (j == params1.count())
+                        candidates << overload;
+                }
+
+                
+                /*
+                    There are several functions with the correct
+                    parameter count, but only one has the correct
+                    types, loosely compared.
+                */
+                if (candidates.count() == 1)
+                    return static_cast<FunctionNode *>(candidates.first());
+
+                return 0;
+            }
+        }
+        delete clone;
+    }
+    return func;
+}
+
+/*!
+  Returns the set of strings reopresenting the topic commands.
+ */
+QSet<QString> CppCodeParser::topicCommands()
+{
+    return QSet<QString>() << COMMAND_CLASS
+                           << 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_SERVICE
+                           << COMMAND_TYPEDEF
+#ifdef QDOC_QML
+                           << COMMAND_VARIABLE
+                           << COMMAND_QMLCLASS
+                           << COMMAND_QMLPROPERTY
+                           << COMMAND_QMLATTACHEDPROPERTY
+                           << COMMAND_QMLSIGNAL
+                           << COMMAND_QMLMETHOD;
+#else
+                           << COMMAND_VARIABLE;
+#endif
+}
+
+/*!
+  Process the topic \a command in context \a doc with argument \a arg.  
+ */
+Node *CppCodeParser::processTopicCommand(const Doc& doc,
+                                         const QString& command,
+                                         const QString& arg)
+{
+    if (command == COMMAND_FN) {
+        QStringList parentPath;
+        FunctionNode *func = 0;
+        FunctionNode *clone = 0;
+
+        if (!makeFunctionNode(arg, &parentPath, &clone) &&
+             !makeFunctionNode("void " + arg, &parentPath, &clone)) {
+            doc.location().warning(tr("Invalid syntax in '\\%1'")
+                                    .arg(COMMAND_FN));
+        }
+        else {
+            if (!usedNamespaces.isEmpty()) {
+                foreach (const QString &usedNamespace, usedNamespaces) {
+                    QStringList newPath = usedNamespace.split("::") + parentPath;
+                    func = tre->findFunctionNode(newPath, clone);
+                    if (func)
+                        break;
+                }
+            }
+            // Search the root namespace if no match was found.
+            if (func == 0)
+                func = tre->findFunctionNode(parentPath, clone);
+
+            if (func == 0) {
+                if (parentPath.isEmpty() && !lastPath.isEmpty())
+                    func = tre->findFunctionNode(lastPath, clone);
+                if (func == 0) {
+                    doc.location().warning(tr("Cannot find '%1' in '\\%2'")
+                                           .arg(clone->name() + "(...)")
+                                           .arg(COMMAND_FN),
+                                           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 {
+                    doc.location().warning(tr("Missing '%1::' for '%2' in '\\%3'")
+                                            .arg(lastPath.join("::"))
+                                            .arg(clone->name() + "()")
+                                            .arg(COMMAND_FN));
+                }
+            }
+            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;
+
+        if (makeFunctionNode(arg, &parentPath, &func, tre->root())) {
+            if (!parentPath.isEmpty()) {
+                doc.location().warning(tr("Invalid syntax in '\\%1'")
+                                        .arg(COMMAND_MACRO));
+                delete func;
+                func = 0;
+            }
+            else {
+                func->setMetaness(FunctionNode::MacroWithParams);
+                QList<Parameter> params = func->parameters();
+                for (int i = 0; i < params.size(); ++i) {
+                    Parameter &param = params[i];
+                    if (param.name().isEmpty() && !param.leftType().isEmpty()
+                            && param.leftType() != "...")
+                        param = Parameter("", "", param.leftType());
+                }
+                func->setParameters(params);
+            }
+            return func;
+        }
+        else if (QRegExp("[A-Za-z_][A-Za-z0-9_]+").exactMatch(arg)) {
+            func = new FunctionNode(tre->root(), arg);
+            func->setAccess(Node::Public);
+            func->setLocation(doc.location());
+            func->setMetaness(FunctionNode::MacroWithoutParams);
+        }
+        else {
+            doc.location().warning(tr("Invalid syntax in '\\%1'")
+                                    .arg(COMMAND_MACRO));
+
+        }
+        return func;
+    }
+    else if (nodeTypeMap.contains(command)) {
+        /*
+          The command was neither "fn" nor "macro" .
+         */
+        // ### split(" ") hack is there to support header file syntax
+        QStringList paths = arg.split(" ");
+        QStringList path = paths[0].split("::");
+        Node *node = 0;
+        if (!usedNamespaces.isEmpty()) {
+            foreach (const QString &usedNamespace, usedNamespaces) {
+                QStringList newPath = usedNamespace.split("::") + path;
+                node = tre->findNode(newPath, nodeTypeMap[command]);
+                if (node) {
+                    path = newPath;
+                    break;
+                }
+            }
+        }
+        // Search the root namespace if no match was found.
+        if (node == 0)
+            node = tre->findNode(path, nodeTypeMap[command]);
+
+        if (node == 0) {
+            doc.location().warning(tr("Cannot find '%1' specified with '\\%2' in any header file")
+                                   .arg(arg).arg(command));
+            lastPath = path;
+
+        }
+        else if (command == COMMAND_SERVICE) {
+            // If the command is "\service", then we need to tag the
+            // class with the actual service name.
+            QStringList args = arg.split(" ");
+            if (args.size() > 1) {
+                ClassNode *cnode = static_cast<ClassNode *>(node);
+                cnode->setServiceName(args[1]);
+                cnode->setHideFromMainList(true);
+            }
+        }
+        else if (node->isInnerNode()) {
+            if (path.size() > 1) {
+                path.pop_back();
+                usedNamespaces.insert(path.join("::"));
+            }
+        }
+
+        if (command == COMMAND_CLASS) {
+            if (paths.size() > 1) {
+                if (!paths[1].endsWith(".h")) {
+                    ClassNode*cnode = static_cast<ClassNode*>(node);
+                    cnode->setQmlElement(paths[1]);
+                }
+            }
+        }
+        return node;
+    }
+    else if (command == COMMAND_EXAMPLE) {
+        FakeNode *fake = new FakeNode(tre->root(), arg, Node::Example);
+        createExampleFileNodes(fake);
+        return fake;
+    }
+    else if (command == COMMAND_EXTERNALPAGE) {
+        return new FakeNode(tre->root(), arg, Node::ExternalPage);
+    }
+    else if (command == COMMAND_FILE) {
+        return new FakeNode(tre->root(), arg, Node::File);
+    }
+    else if (command == COMMAND_GROUP) {
+        return new FakeNode(tre->root(), arg, Node::Group);
+    }
+    else if (command == COMMAND_HEADERFILE) {
+        return new FakeNode(tre->root(), arg, Node::HeaderFile);
+    }
+    else if (command == COMMAND_MODULE) {
+        return new FakeNode(tre->root(), arg, Node::Module);
+    }
+    else if (command == COMMAND_PAGE) {
+        return new FakeNode(tre->root(), arg, Node::Page);
+    }
+#ifdef QDOC_QML
+    else if (command == COMMAND_QMLCLASS) {
+        const ClassNode* classNode = 0;
+        QStringList names = arg.split(" ");
+        if (names.size() > 1) {
+            Node* n = tre->findNode(names[1].split("::"),Node::Class);
+            if (n)
+                classNode = static_cast<const ClassNode*>(n);
+        }
+        return new QmlClassNode(tre->root(), names[0], classNode);
+    }
+    else if ((command == COMMAND_QMLSIGNAL) ||
+             (command == COMMAND_QMLMETHOD)) {
+        QString element;
+        QString name;
+        QmlClassNode* qmlClass = 0;
+        if (splitQmlArg(doc,arg,element,name)) {
+            Node* n = tre->findNode(QStringList(element),Node::Fake);
+            if (n && n->subType() == Node::QmlClass) {
+                qmlClass = static_cast<QmlClassNode*>(n);
+                if (command == COMMAND_QMLSIGNAL)
+                    return new QmlSignalNode(qmlClass,name);
+                else
+                    return new QmlMethodNode(qmlClass,name);
+            }
+        }
+    }
+#endif
+    return 0;
+}
+
+#ifdef QDOC_QML
+
+/*!
+  A QML property argument has the form...
+
+  <type> <element>::<name>
+
+  This function splits the argument into those three
+  parts, sets \a type, \a element, and \a property,
+  and returns true. If any of the parts isn't found,
+  a debug message is output and false is returned.
+ */
+bool CppCodeParser::splitQmlPropertyArg(const Doc& doc,
+                                        const QString& arg,
+                                        QString& type,
+                                        QString& element,
+                                        QString& property)
+{
+    QStringList blankSplit = arg.split(" ");
+    if (blankSplit.size() > 1) {
+        type = blankSplit[0];
+        QStringList colonSplit(blankSplit[1].split("::"));
+        if (colonSplit.size() > 1) {
+            element = colonSplit[0];
+            property = colonSplit[1];
+            return true;
+        }
+        else
+            doc.location().warning(tr("Missing QML element name or property name"));
+    }
+    else
+        doc.location().warning(tr("Missing QML property type or property path"));
+    return false;
+}
+
+/*!
+  A QML signal or method argument has the form...
+
+  <element>::<name>
+
+  This function splits the argument into those two
+  parts, sets \a element, and \a name, and returns
+  true. If either of the parts isn't found, a debug
+  message is output and false is returned.
+ */
+bool CppCodeParser::splitQmlArg(const Doc& doc,
+                                const QString& arg,
+                                QString& element,
+                                QString& name)
+{
+    QStringList colonSplit(arg.split("::"));
+    if (colonSplit.size() > 1) {
+        element = colonSplit[0];
+        name = colonSplit[1];
+        return true;
+    }
+    else
+        doc.location().warning(tr("Missing QML element name or signal/method name"));
+    return false;
+}
+
+/*!
+  Process the topic \a command group with arguments \a args.
+
+  Currently, this function is called only for \e{qmlproperty}
+  and \e{qmlattachedproperty}.
+ */
+Node *CppCodeParser::processTopicCommandGroup(const Doc& doc,
+                                              const QString& command,
+                                              const QStringList& args)
+{
+    QmlPropGroupNode* qmlPropGroup = 0;
+    if ((command == COMMAND_QMLPROPERTY) ||
+        (command == COMMAND_QMLATTACHEDPROPERTY)) {
+        QString type;
+        QString element;
+        QString property;
+        bool attached = (command == COMMAND_QMLATTACHEDPROPERTY);
+        QStringList::ConstIterator arg = args.begin();
+        if (splitQmlPropertyArg(doc,(*arg),type,element,property)) {
+            Node* n = tre->findNode(QStringList(element),Node::Fake);
+            if (n && n->subType() == Node::QmlClass) {
+                QmlClassNode* qmlClass = static_cast<QmlClassNode*>(n);
+                if (qmlClass)
+                    qmlPropGroup = new QmlPropGroupNode(qmlClass,
+                                                        property,
+                                                        attached);
+            }
+        }
+        if (qmlPropGroup) {
+            new QmlPropertyNode(qmlPropGroup,property,type,attached);
+            ++arg;
+            while (arg != args.end()) {
+                if (splitQmlPropertyArg(doc,(*arg),type,element,property)) {
+                    new QmlPropertyNode(qmlPropGroup,
+                                        property,
+                                        type,
+                                        attached);
+                }
+                ++arg;
+            }
+        }
+    }
+    return qmlPropGroup;
+}
+#endif
+
+/*!
+  Returns the set of strings representing the common metacommands
+  plus some other metacommands.
+ */
+QSet<QString> CppCodeParser::otherMetaCommands()
+{
+    return commonMetaCommands() << COMMAND_INHEADERFILE
+                                << COMMAND_OVERLOAD
+                                << COMMAND_REIMP
+                                << COMMAND_RELATES
+                                << COMMAND_CONTENTSPAGE
+                                << COMMAND_NEXTPAGE
+                                << COMMAND_PREVIOUSPAGE
+                                << COMMAND_INDEXPAGE
+#ifdef QDOC_QML        
+                                << COMMAND_STARTPAGE
+                                << COMMAND_QMLINHERITS
+                                << COMMAND_QMLDEFAULT;
+#else    
+                                << COMMAND_STARTPAGE;
+#endif    
+}
+
+/*!
+  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 QString& arg,
+                                            Node *node)
+{
+    if (command == COMMAND_INHEADERFILE) {
+        if (node != 0 && node->isInnerNode()) {
+            ((InnerNode *) node)->addInclude(arg);
+        }
+        else {
+            doc.location().warning(tr("Ignored '\\%1'")
+                                   .arg(COMMAND_INHEADERFILE));
+        }
+    }
+    else if (command == COMMAND_OVERLOAD) {
+        if (node != 0 && node->type() == Node::Function) {
+            ((FunctionNode *) node)->setOverload(true);
+        }
+        else {
+            doc.location().warning(tr("Ignored '\\%1'")
+                                   .arg(COMMAND_OVERLOAD));
+        }
+    }
+    else if (command == COMMAND_REIMP) {
+        if (node != 0 && 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()));
+            }
+
+#if 0
+            // Reimplemented functions now reported in separate sections.
+            /*
+              Note: Setting the access to Private hides the documentation,
+              but setting the status to Internal makes the node available
+              in the XML output when the WebXMLGenerator is used.
+            */
+            func->setAccess(Node::Private);
+            func->setStatus(Node::Internal);
+#endif
+            func->setReimp(true);
+        }
+        else {
+            doc.location().warning(tr("Ignored '\\%1' in %2")
+                                   .arg(COMMAND_REIMP)
+                                   .arg(node->name()));
+        }
+    }
+    else if (command == COMMAND_RELATES) {
+        InnerNode *pseudoParent;
+        if (arg.startsWith("<") || arg.startsWith("\"")) {
+            pseudoParent =
+                static_cast<InnerNode *>(tre->findNode(QStringList(arg),
+                                                       Node::Fake));
+        }
+        else {
+            QStringList newPath = arg.split("::");
+            pseudoParent =
+                static_cast<InnerNode*>(tre->findNode(QStringList(newPath),
+                                                      Node::Class));
+            if (!pseudoParent)
+                pseudoParent =
+                    static_cast<InnerNode*>(tre->findNode(QStringList(newPath),
+                                                          Node::Namespace));
+        }
+        if (!pseudoParent) {
+            doc.location().warning(tr("Cannot find '%1' in '\\%2'")
+                                   .arg(arg).arg(COMMAND_RELATES));
+        }
+        else {
+            node->setRelates(pseudoParent);
+        }
+    }
+    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);
+    }
+#ifdef QDOC_QML
+    else if (command == COMMAND_QMLINHERITS) {
+        setLink(node, Node::InheritsLink, arg);
+    }
+    else if (command == COMMAND_QMLDEFAULT) {
+        QmlPropGroupNode* qpgn = static_cast<QmlPropGroupNode*>(node);
+        qpgn->setDefault();
+    }
+#endif
+    else {
+        processCommonMetaCommand(doc.location(),command,arg,node,tre);
+    }
+}
+
+/*!
+  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.begin();
+    while (cmd != metaCommands.end()) {
+        QStringList args = doc.metaCommandArgs(*cmd);
+        QStringList::ConstIterator arg = args.begin();
+        while (arg != args.end()) {
+            processOtherMetaCommand(doc, *cmd, *arg, node);
+            ++arg;
+        }
+        ++cmd;
+    }
+}
+
+/*!
+  Resets the C++ code parser to its default initialized state.
+ */
+void CppCodeParser::reset(Tree *tree)
+{
+    tre = tree;
+    tokenizer = 0;
+    tok = 0;
+    access = Node::Public;
+    metaness = FunctionNode::Plain;
+    lastPath.clear();
+    moduleName = "";
+}
+
+/*!
+  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;
+    }
+    else
+        return false;
+}
+
+/*!
+  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::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;
+}
+
+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))
+                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;
+}
+
+bool CppCodeParser::matchParameter(FunctionNode *func)
+{
+    CodeChunk dataType;
+    QString name;
+    CodeChunk defaultValue;
+
+    if (!matchDataType(&dataType, &name))
+        return false;
+    match(Tok_Comment);
+    if (match(Tok_Equal)) {
+        int parenDepth0 = tokenizer->parenDepth();
+
+        while (tokenizer->parenDepth() >= parenDepth0 &&
+                (tok != Tok_Comma ||
+                 tokenizer->parenDepth() > parenDepth0) &&
+                tok != Tok_Eoi) {
+            defaultValue.append(lexeme());
+            readToken();
+        }
+    }
+    func->addParameter(Parameter(dataType.toString(),
+                                 "",
+                                 name,
+                                 defaultValue.toString())); // ###
+    return true;
+}
+
+bool CppCodeParser::matchFunctionDecl(InnerNode *parent,
+                                      QStringList *parentPathPtr,
+                                      FunctionNode **funcPtr,
+                                      const QString &templateStuff)
+{
+    CodeChunk returnType;
+    QStringList parentPath;
+    QString name;
+
+    bool compat = false;
+
+    if (match(Tok_friend))
+        return false;
+    match(Tok_explicit);
+    if (matchCompat())
+        compat = true;
+    bool sta = false;
+    if (match(Tok_static)) {
+        sta = true;
+        if (matchCompat())
+            compat = true;
+    }
+    FunctionNode::Virtualness vir = FunctionNode::NonVirtual;
+    if (match(Tok_virtual)) {
+        vir = FunctionNode::ImpureVirtual;
+        if (matchCompat())
+            compat = true;
+    }
+
+    if (!matchDataType(&returnType)) {
+        if (tokenizer->parsingFnOrMacro()
+                && (match(Tok_Q_DECLARE_FLAGS) || match(Tok_Q_PROPERTY)))
+            returnType = CodeChunk(previousLexeme());
+        else
+            return false;
+    }
+
+    if (returnType.toString() == "QBool")
+        returnType = CodeChunk("bool");
+
+    if (matchCompat())
+        compat = true;
+
+    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();
+            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 (compat)
+                var->setStatus(Node::Compat);
+            var->setStatic(sta);
+            return false;
+        }
+        if (tok != Tok_LeftParen)
+            return false;
+    }
+    readToken();
+
+    FunctionNode *func = new FunctionNode(parent, name);
+    func->setAccess(access);
+    func->setLocation(location());
+    func->setReturnType(returnType.toString());
+    func->setParentPath(parentPath);
+    func->setTemplateStuff(templateStuff);
+    if (compat)
+        func->setStatus(Node::Compat);
+
+    func->setMetaness(metaness);
+    if (parent) {
+        if (name == parent->name()) {
+            func->setMetaness(FunctionNode::Ctor);
+        } else if (name.startsWith("~"))  {
+            func->setMetaness(FunctionNode::Dtor);
+        }
+    }
+    func->setStatic(sta);
+
+    if (tok != Tok_RightParen) {
+        do {
+            if (!matchParameter(func))
+                return false;
+        } while (match(Tok_Comma));
+    }
+    if (!match(Tok_RightParen))
+        return false;
+
+    func->setConst(match(Tok_const));
+
+    if (match(Tok_Equal) && match(Tok_Number))
+        vir = FunctionNode::PureVirtual;
+    func->setVirtualness(vir);
+
+    if (match(Tok_Colon)) {
+        while (tok != Tok_LeftBrace && tok != Tok_Eoi)
+            readToken();
+    }
+
+    if (!match(Tok_Semicolon) && tok != Tok_Eoi) {
+        int braceDepth0 = tokenizer->braceDepth();
+
+        if (!match(Tok_LeftBrace))
+            return false;
+        while (tokenizer->braceDepth() >= braceDepth0 && tok != Tok_Eoi)
+            readToken();
+        match(Tok_RightBrace);
+    }
+    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;
+
+    tre->addBaseClass(classe,
+                      access,
+                      baseClass.toPath(),
+                      baseClass.toString(),
+                      classe->parent());
+    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 declarion.
+ */
+bool CppCodeParser::matchClassDecl(InnerNode *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_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 (!moduleName.isEmpty())
+        classe->setModuleName(moduleName);
+    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(InnerNode *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 *namespasse = 0;
+    if (parent)
+        namespasse = static_cast<NamespaceNode*>(parent->findNode(namespaceName, Node::Namespace));
+    if (!namespasse) {
+        namespasse = new NamespaceNode(parent, namespaceName);
+        namespasse->setAccess(access);
+        namespasse->setLocation(location());
+    }
+
+    readToken(); // skip '{'
+    bool matched = matchDeclList(namespasse);
+
+    return matched && match(Tok_RightBrace);
+}
+
+bool CppCodeParser::matchUsingDecl()
+{
+    readToken(); // skip 'using'
+
+    // 'namespace'
+    if (tok != Tok_namespace)
+        return false;
+
+    readToken();
+    // identifier
+    if (tok != Tok_Ident)
+        return false;
+
+    QString name;
+    while (tok == Tok_Ident) {
+        name += lexeme();
+        readToken();
+        if (tok == Tok_Semicolon)
+            break;
+        else if (tok != Tok_Gulbrandsen)
+            return false;
+        name += "::";
+        readToken();
+    }
+
+    /*
+        So far, so good. We have 'using namespace Foo;'.
+    */
+    usedNamespaces.insert(name);
+    return true;
+}
+
+bool CppCodeParser::matchEnumItem(InnerNode *parent, EnumNode *enume)
+{
+    if (!match(Tok_Ident))
+        return false;
+
+    QString name = previousLexeme();
+    CodeChunk val;
+
+    if (match(Tok_Equal)) {
+        while (tok != Tok_Comma && tok != Tok_RightBrace &&
+                tok != Tok_Eoi) {
+            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("0") && last.size() > 1) {
+                        if (last.startsWith("0x") || last.startsWith("0X"))
+                            strVal = last.left(2) + QString::number(n + 1, 16);
+                        else
+                            strVal = "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(InnerNode *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(InnerNode *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->findNode(name, Node::Typedef)) {
+        TypedefNode *typedeffe = new TypedefNode(parent, name);
+        typedeffe->setAccess(access);
+        typedeffe->setLocation(location());
+    }
+    return true;
+}
+
+bool CppCodeParser::matchProperty(InnerNode *parent)
+{
+    if (!match(Tok_Q_PROPERTY) &&
+        !match(Tok_Q_OVERRIDE) &&
+        !match(Tok_QDOC_PROPERTY))
+        return false;
+    if (!match(Tok_LeftParen))
+        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;
+
+        if (match(Tok_Ident)) {
+            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")
+            tre->addPropertyFunction(property, value, PropertyNode::Getter);
+        else if (key == "WRITE")
+            tre->addPropertyFunction(property, value, PropertyNode::Setter);
+        else if (key == "STORED")
+            property->setStored(value.toLower() == "true");
+        else if (key == "DESIGNABLE")
+            property->setDesignable(value.toLower() == "true");
+        else if (key == "RESET")
+            tre->addPropertyFunction(property, value, PropertyNode::Resetter);
+
+        else if (key == "NOTIFY") {
+            tre->addPropertyFunction(property, value, PropertyNode::Notifier);
+        }
+
+    }
+    match(Tok_RightParen);
+    return true;
+}
+
+/*!
+  Parse a C++ declaration.
+ */
+bool CppCodeParser::matchDeclList(InnerNode *parent)
+{
+    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();
+            break;
+        case Tok_template:
+            templateStuff = matchTemplateHeader();
+            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_QDOC_PROPERTY:
+            matchProperty(parent);
+            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 enumType = previousLexeme();
+                    TypedefNode *flagsNode = new TypedefNode(parent, flagsType);
+                    flagsNode->setAccess(access);
+                    flagsNode->setLocation(location());
+                    EnumNode *enumNode =
+                        static_cast<EnumNode*>(parent->findNode(enumType,
+                                                                Node::Enum));
+                    if (enumNode)
+                        enumNode->setFlagsType(flagsNode);
+                }
+            }
+            match(Tok_RightParen);
+            break;
+        case Tok_QT_MODULE:
+            readToken();
+            if (match(Tok_LeftParen) && match(Tok_Ident))
+                moduleName = previousLexeme();
+            if (!moduleName.startsWith("Qt"))
+                moduleName.prepend("Qt");
+            match(Tok_RightParen);
+            break;
+        default:
+            if (!matchFunctionDecl(parent, 0, 0, templateStuff)) {
+                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()
+{
+    QSet<QString> topicCommandsAllowed = topicCommands();
+    QSet<QString> otherMetacommandsAllowed = otherMetaCommands();
+    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);
+
+            QString topic;
+            QStringList args;
+
+            QSet<QString> topicCommandsUsed = topicCommandsAllowed &
+                doc.metaCommandsUsed();
+
+            /*
+              There should be one topic command in the set,
+              or none. If the set is empty, then the comment
+              should be a function description.
+             */
+            if (topicCommandsUsed.count() > 0) {
+                topic = *topicCommandsUsed.begin();
+                args = doc.metaCommandArgs(topic);
+            }
+
+            NodeList nodes;
+            QList<Doc> docs;
+
+            if (topic.isEmpty()) {
+                QStringList parentPath;
+                FunctionNode *clone;
+                FunctionNode *func = 0;
+
+                if (matchFunctionDecl(0, &parentPath, &clone)) {
+                    foreach (const QString &usedNamespace, usedNamespaces) {
+                        QStringList newPath = usedNamespace.split("::") + parentPath;
+                        func = tre->findFunctionNode(newPath, clone);
+                        if (func)
+                            break;
+                    }
+                    if (func == 0)
+                        func = tre->findFunctionNode(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 {
+                /*
+                  There is a topic command. Process it.
+                 */
+#ifdef QDOC_QML
+                if ((topic == COMMAND_QMLPROPERTY) ||
+                    (topic == COMMAND_QMLATTACHEDPROPERTY)) {
+                    Doc nodeDoc = doc;
+                    Node *node = processTopicCommandGroup(nodeDoc,topic,args);
+                    if (node != 0) {
+                        nodes.append(node);
+                        docs.append(nodeDoc);
+                    }
+                }
+                else {
+                    QStringList::ConstIterator a = args.begin();
+                    while (a != args.end()) {
+                        Doc nodeDoc = doc;
+                        Node *node = processTopicCommand(nodeDoc,topic,*a);
+                        if (node != 0) {
+                            nodes.append(node);
+                            docs.append(nodeDoc);
+                        }
+                        ++a;
+                    }
+                }
+#else
+                QStringList::ConstIterator a = args.begin();
+                while (a != args.end()) {
+                    Doc nodeDoc = doc;
+                    Node *node = processTopicCommand(nodeDoc, topic, *a);
+                    if (node != 0) {
+                        nodes.append(node);
+                        docs.append(nodeDoc);
+                    }
+                    ++a;
+                }
+#endif                
+            }
+
+            NodeList::Iterator n = nodes.begin();
+            QList<Doc>::Iterator d = docs.begin();
+            while (n != nodes.end()) {
+                processOtherMetaCommands(*d, *n);
+                (*n)->setDoc(*d);
+                if ((*n)->isInnerNode() &&
+                    ((InnerNode *)*n)->includes().isEmpty()) {
+                    InnerNode *m = static_cast<InnerNode *>(*n);
+                    while (m->parent() != tre->root())
+                        m = m->parent();
+                    if (m == *n)
+                        ((InnerNode *)*n)->addInclude((*n)->name());
+                    else
+                        ((InnerNode *)*n)->setIncludes(m->includes());
+                }
+                ++d;
+                ++n;
+            }
+        }
+        else if (tok == Tok_using) {
+            matchUsingDecl();
+        }
+        else {
+            QStringList parentPath;
+            FunctionNode *clone;
+            FunctionNode *node = 0;
+
+            if (matchFunctionDecl(0, &parentPath, &clone)) {
+                /*
+                  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 = tre->findFunctionNode(parentPath, clone);
+                if (node != 0 && node->metaness() != FunctionNode::Signal)
+                    node->setLocation(clone->location());
+                delete clone;
+            }
+            else {
+                if (tok != Tok_Doc)
+                    readToken();
+            }
+        }
+    }
+    return true;
+}
+
+bool CppCodeParser::makeFunctionNode(const QString& synopsis,
+                                     QStringList *parentPathPtr,
+                                     FunctionNode **funcPtr,
+                                     InnerNode *root)
+{
+    Tokenizer *outerTokenizer = tokenizer;
+    int outerTok = tok;
+
+    Location loc;
+    QByteArray latin1 = synopsis.toLatin1();
+    Tokenizer stringTokenizer(loc, latin1);
+    stringTokenizer.setParsingFnOrMacro(true);
+    tokenizer = &stringTokenizer;
+    readToken();
+
+    bool ok = matchFunctionDecl(root, parentPathPtr, funcPtr);
+    // potential memory leak with funcPtr
+
+    tokenizer = outerTokenizer;
+    tok = outerTok;
+
+    return ok;
+}
+
+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.replace("\\\n", "");
+    QStringList lines = text.split("\n");
+    lines = lines.filter("Q_DECLARE");
+    lines.replaceInStrings(QRegExp("#define Q[A-Z_]*\\(C\\)"), "");
+
+    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,
+                                             Tree * /* tree */)
+{
+    QString resultingCode = macroDef;
+    resultingCode.replace(QRegExp("\\bC\\b"), container);
+    resultingCode.replace(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(tre->root());
+}
+
+void CppCodeParser::createExampleFileNodes(FakeNode *fake)
+{
+    QString examplePath = fake->name();
+
+    // we can assume that this file always exists
+    QString proFileName = examplePath + "/" +
+        examplePath.split("/").last() + ".pro";
+
+    QString userFriendlyFilePath;
+    QString fullPath = Config::findFile(fake->doc().location(),
+                                        exampleFiles,
+                                        exampleDirs,
+                                        proFileName,
+                                        userFriendlyFilePath);
+    if (fullPath.isEmpty()) {
+        QString tmp = proFileName;
+        proFileName = examplePath + "/" + "qbuild.pro";
+        userFriendlyFilePath.clear();
+        fullPath = Config::findFile(fake->doc().location(),
+                                    exampleFiles,
+                                    exampleDirs,
+                                    proFileName,
+                                    userFriendlyFilePath);
+        if (fullPath.isEmpty()) {
+            fake->doc().location().warning(
+               tr("Cannot find file '%1' or '%2'").arg(tmp).arg(proFileName));
+            return;
+        }
+    }
+
+    int sizeOfBoringPartOfName = fullPath.size() - proFileName.size();
+    fullPath.truncate(fullPath.lastIndexOf('/'));
+
+    QStringList exampleFiles = Config::getFilesHere(fullPath,
+                                                    exampleNameFilter);
+    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");
+    }
+
+    foreach (const QString &exampleFile, exampleFiles)
+        (void) new FakeNode(fake,
+                            exampleFile.mid(sizeOfBoringPartOfName),
+                            Node::File);
+}
+
+QT_END_NAMESPACE