| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 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$ |
| ** GNU Lesser General Public License Usage |
| ** 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. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $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 "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_QMLATTACHEDSIGNAL Doc::alias("qmlattachedsignal") |
| #define COMMAND_QMLMETHOD Doc::alias("qmlmethod") |
| #define COMMAND_QMLATTACHEDMETHOD Doc::alias("qmlattachedmethod") |
| #define COMMAND_QMLDEFAULT Doc::alias("default") |
| #define COMMAND_QMLBASICTYPE Doc::alias("qmlbasictype") |
| #endif |
| |
| #define COMMAND_AUDIENCE Doc::alias("audience") |
| #define COMMAND_CATEGORY Doc::alias("category") |
| #define COMMAND_PRODNAME Doc::alias("prodname") |
| #define COMMAND_COMPONENT Doc::alias("component") |
| #define COMMAND_AUTHOR Doc::alias("author") |
| #define COMMAND_PUBLISHER Doc::alias("publisher") |
| #define COMMAND_COPYRYEAR Doc::alias("copyryear") |
| #define COMMAND_COPYRHOLDER Doc::alias("copyrholder") |
| #define COMMAND_PERMISSIONS Doc::alias("permissions") |
| #define COMMAND_LIFECYCLEVERSION Doc::alias("lifecycleversion") |
| #define COMMAND_LIFECYCLEWSTATUS Doc::alias("lifecyclestatus") |
| #define COMMAND_LICENSEYEAR Doc::alias("licenseyear") |
| #define COMMAND_LICENSENAME Doc::alias("licensename") |
| #define COMMAND_LICENSEDESCRIPTION Doc::alias("licensedescription") |
| #define COMMAND_RELEASEDATE Doc::alias("releasedate") |
| |
| 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() |
| { |
| // 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); |
| |
| 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"; |
| |
| 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 big \a tree. The |
| \a location is used for reporting errors. |
| */ |
| void CppCodeParser::parseHeaderFile(const Location& location, |
| const QString& filePath, |
| Tree *tree) |
| { |
| QFile in(filePath); |
| if (!in.open(QIODevice::ReadOnly)) { |
| 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()); |
| in.close(); |
| |
| 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) |
| { |
| QFile in(filePath); |
| if (!in.open(QIODevice::ReadOnly)) { |
| 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(); |
| in.close(); |
| } |
| |
| /*! |
| This is called after all the header files have been parsed. |
| I think the most important thing it does is resolve class |
| inheritance links in the tree. But it also initializes a |
| bunch of stuff. |
| */ |
| 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(); |
| } |
| |
| /*! |
| This is called after all the source files (i.e., not the |
| header files) have been parsed. It traverses the tree to |
| resolve property links, normalize overload signatures, and |
| do other housekeeping of the tree. |
| */ |
| void CppCodeParser::doneParsingSourceFiles(Tree *tree) |
| { |
| tree->root()->makeUndocumentedChildrenInternal(); |
| tree->root()->normalizeOverloads(); |
| tree->fixInheritance(); |
| tree->resolveProperties(); |
| } |
| |
| /*! |
| This function searches the \a tree to find a FunctionNode |
| for a function with the signature \a synopsis. If the |
| \a relative node is provided, the search begins there. If |
| \a fuzzy is true, base classes are searched. The function |
| node is returned, if found. |
| */ |
| 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_QMLATTACHEDSIGNAL |
| << COMMAND_QMLMETHOD |
| << COMMAND_QMLATTACHEDMETHOD |
| << COMMAND_QMLBASICTYPE; |
| #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 ¶m = 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); |
| } |
| if (names[0].startsWith("Qt")) |
| return new QmlClassNode(tre->root(), QLatin1String("QML:")+names[0], classNode); |
| else |
| return new QmlClassNode(tre->root(), names[0], classNode); |
| } |
| else if (command == COMMAND_QMLBASICTYPE) { |
| return new QmlBasicTypeNode(tre->root(), arg); |
| } |
| else if ((command == COMMAND_QMLSIGNAL) || |
| (command == COMMAND_QMLMETHOD) || |
| (command == COMMAND_QMLATTACHEDSIGNAL) || |
| (command == COMMAND_QMLATTACHEDMETHOD)) { |
| QString element; |
| QString type; |
| QmlClassNode* qmlClass = 0; |
| if (splitQmlMethodArg(doc,arg,type,element)) { |
| if (element.startsWith(QLatin1String("Qt"))) |
| element = QLatin1String("QML:") + element; |
| Node* n = tre->findNode(QStringList(element),Node::Fake); |
| if (n && n->subType() == Node::QmlClass) { |
| qmlClass = static_cast<QmlClassNode*>(n); |
| if (command == COMMAND_QMLSIGNAL) |
| return makeFunctionNode(doc,arg,qmlClass,Node::QmlSignal,false,COMMAND_QMLSIGNAL); |
| else if (command == COMMAND_QMLATTACHEDSIGNAL) |
| return makeFunctionNode(doc,arg,qmlClass,Node::QmlSignal,true,COMMAND_QMLATTACHEDSIGNAL); |
| else if (command == COMMAND_QMLMETHOD) |
| return makeFunctionNode(doc,arg,qmlClass,Node::QmlMethod,false,COMMAND_QMLMETHOD); |
| else if (command == COMMAND_QMLATTACHEDMETHOD) |
| return makeFunctionNode(doc,arg,qmlClass,Node::QmlMethod,true,COMMAND_QMLATTACHEDMETHOD); |
| else |
| return 0; // never get here. |
| } |
| } |
| } |
| #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 name, |
| and returns true. If any of the parts isn't found, |
| a qdoc warning is output and false is returned. |
| */ |
| bool CppCodeParser::splitQmlPropertyArg(const Doc& doc, |
| const QString& arg, |
| QString& type, |
| QString& element, |
| QString& name) |
| { |
| QStringList blankSplit = arg.split(" "); |
| if (blankSplit.size() > 1) { |
| type = blankSplit[0]; |
| QStringList colonSplit(blankSplit[1].split("::")); |
| if (colonSplit.size() > 1) { |
| element = colonSplit[0]; |
| name = colonSplit[1]; |
| return true; |
| } |
| else |
| doc.location().warning(tr("Missing parent QML element name")); |
| } |
| else |
| doc.location().warning(tr("Missing property type")); |
| return false; |
| } |
| |
| /*! |
| A QML signal or method argument has the form... |
| |
| <type> <element>::<name>(<param>, <param>, ...) |
| |
| 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::splitQmlMethodArg(const Doc& doc, |
| const QString& arg, |
| QString& type, |
| QString& element) |
| { |
| QStringList colonSplit(arg.split("::")); |
| if (colonSplit.size() > 1) { |
| QStringList blankSplit = colonSplit[0].split(" "); |
| if (blankSplit.size() > 1) { |
| type = blankSplit[0]; |
| element = blankSplit[1]; |
| } |
| else { |
| type = QString(""); |
| element = colonSplit[0]; |
| } |
| return true; |
| } |
| else |
| doc.location().warning(tr("Missing parent QML element or method signature")); |
| 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) { |
| const ClassNode *correspondingClass = static_cast<const QmlClassNode*>(qmlPropGroup->parent())->classNode(); |
| QmlPropertyNode *qmlPropNode = new QmlPropertyNode(qmlPropGroup,property,type,attached); |
| |
| const PropertyNode *correspondingProperty = 0; |
| if (correspondingClass) { |
| correspondingProperty = qmlPropNode->correspondingProperty(tre); |
| } |
| if (correspondingProperty) { |
| bool writableList = type.startsWith("list") && correspondingProperty->dataType().endsWith('*'); |
| qmlPropNode->setWritable(writableList || correspondingProperty->isWritable()); |
| } |
| ++arg; |
| while (arg != args.end()) { |
| if (splitQmlPropertyArg(doc,(*arg),type,element,property)) { |
| QmlPropertyNode* qmlPropNode = new QmlPropertyNode(qmlPropGroup, |
| property, |
| type, |
| attached); |
| if (correspondingProperty) { |
| bool writableList = type.startsWith("list") && correspondingProperty->dataType().endsWith('*'); |
| qmlPropNode->setWritable(writableList || correspondingProperty->isWritable()); |
| } |
| } |
| ++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())); |
| } |
| |
| 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); |
| if (node->subType() == Node::QmlClass) { |
| QmlClassNode::addInheritedBy(arg,node); |
| } |
| } |
| 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; |
| } |
| |
| /*! |
| 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 ? true : 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, |
| Node::Type type, |
| bool attached) |
| { |
| 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) || |
| match(Tok_Q_PRIVATE_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(type, parent, name, attached); |
| 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) |
| { |
| 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; |
| |
| 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") |
| tre->addPropertyFunction(property, value, PropertyNode::Getter); |
| else if (key == "WRITE") { |
| tre->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") |
| tre->addPropertyFunction(property, value, PropertyNode::Resetter); |
| else if (key == "NOTIFY") { |
| tre->addPropertyFunction(property, value, PropertyNode::Notifier); |
| } else if (key == "REVISION") { |
| int revision; |
| bool ok; |
| revision = value.toInt(&ok); |
| if (ok) |
| property->setRevision(revision); |
| else |
| parent->doc().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); |
| } |
| } |
| else if (key == "CONSTANT") |
| property->setConstant(); |
| else if (key == "FINAL") |
| property->setFinal(); |
| } |
| 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_Q_PRIVATE_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, |
| Node::Type type, |
| bool attached) |
| { |
| 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, QString(), type, attached); |
| // potential memory leak with funcPtr |
| |
| tokenizer = outerTokenizer; |
| tok = outerTok; |
| return ok; |
| } |
| |
| /*! |
| 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. |
| */ |
| FunctionNode* CppCodeParser::makeFunctionNode(const Doc& doc, |
| const QString& sig, |
| InnerNode* parent, |
| Node::Type type, |
| bool attached, |
| QString qdoctag) |
| { |
| QStringList pp; |
| FunctionNode* fn = 0; |
| if (!makeFunctionNode(sig,&pp,&fn,parent,type,attached) && |
| !makeFunctionNode("void "+sig,&pp,&fn,parent,type,attached)) { |
| doc.location().warning(tr("Invalid syntax in '\\%1'").arg(qdoctag)); |
| } |
| if (fn) |
| return fn; |
| return 0; |
| } |
| |
| 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 ¯oDef, |
| 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(); |
| 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()) { |
| proFileName = examplePath + "/" + examplePath.split("/").last() + ".qmlproject"; |
| 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); |
| 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 qmldir"); |
| } |
| |
| foreach (const QString &exampleFile, exampleFiles) |
| (void) new FakeNode(fake, |
| exampleFile.mid(sizeOfBoringPartOfName), |
| Node::File); |
| foreach (const QString &imageFile, imageFiles) { |
| new FakeNode(fake, |
| imageFile.mid(sizeOfBoringPartOfName), |
| Node::Image); |
| } |
| } |
| |
| QT_END_NAMESPACE |