/**************************************************************************** | |
** | |
** 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 |