Files
qt-creator/tests/tools/cplusplus-ast2png/cplusplus-ast2png.cpp
Nikolai Kosjar a281dfd4b1 C++: Tools: Compile with QT_NO_CAST_FROM_ASCII
Change-Id: I9de2c7fe51fea7844813cecb7c68f9a85a8e1e62
Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com>
2012-12-06 10:56:10 +01:00

495 lines
16 KiB
C++

/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 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, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include <AST.h>
#include <ASTVisitor.h>
#include <ASTPatternBuilder.h>
#include <ASTMatcher.h>
#include <Control.h>
#include <Scope.h>
#include <TranslationUnit.h>
#include <Literals.h>
#include <Symbols.h>
#include <Names.h>
#include <CoreTypes.h>
#include <CppDocument.h>
#include <SymbolVisitor.h>
#include <Overview.h>
#include "cplusplus-tools-utils.h"
#include <QDir>
#include <QFile>
#include <QList>
#include <QCoreApplication>
#include <QStringList>
#include <QFileInfo>
#include <QTime>
#include <QDebug>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#ifdef __GNUC__
# include <cxxabi.h>
#endif
// For isatty(), _isatty()
#if defined(Q_OS_WIN)
# include <io.h>
#else
# include <unistd.h>
#endif
bool tty_for_stdin()
{
#if defined(Q_OS_WIN)
return _isatty(_fileno(stdin));
#else
return isatty(fileno(stdin));
#endif
}
using namespace CPlusPlus;
class ASTDump: protected ASTVisitor
{
public:
ASTDump(TranslationUnit *unit)
: ASTVisitor(unit) {}
void operator()(AST *ast) {
QByteArray basename = translationUnit()->fileName();
basename.append(".ast.dot");
out.open(basename.constData());
out << "digraph AST { ordering=out;" << std::endl;
// std::cout << "rankdir = \"LR\";" << std::endl;
generateTokens();
accept(ast);
typedef QPair<QByteArray, QByteArray> Pair;
foreach (const Pair &conn, _connections)
out << conn.first.constData() << " -> " << conn.second.constData() << std::endl;
alignTerminals();
out << "}" << std::endl;
out.close();
}
// the following file can be generated by using:
// cplusplus-update-frontend <frontend-dir> <dumpers-file>
#include "dumpers.inc"
protected:
void alignTerminals() {
out<<"{ rank=same;" << std::endl;
foreach (const QByteArray &terminalShape, _terminalShapes) {
out << " " << std::string(terminalShape) << ";" << std::endl;
}
out<<"}"<<std::endl;
}
static QByteArray name(AST *ast) {
#ifdef __GNUC__
QByteArray name = abi::__cxa_demangle(typeid(*ast).name(), 0, 0, 0) + 11;
name.truncate(name.length() - 3);
#else
QByteArray name = typeid(*ast).name();
#endif
return name;
}
QByteArray terminalId(unsigned token)
{ return 't' + QByteArray::number(token); }
void terminal(unsigned token, AST *node) {
_connections.append(qMakePair(_id[node], terminalId(token)));
}
void generateTokens() {
for (unsigned token = 1; token < translationUnit()->tokenCount(); ++token) {
if (translationUnit()->tokenKind(token) == T_EOF_SYMBOL)
break;
QByteArray t;
t.append(terminalId(token));
t.append(" [shape=rect label = \"");
t.append(spell(token));
t.append("\"]");
if (token > 1) {
t.append("; ");
t.append(terminalId(token - 1));
t.append(" -> ");
t.append(terminalId(token));
t.append(" [arrowhead=\"vee\" color=\"transparent\"]");
}
_terminalShapes.append(t);
}
}
virtual void nonterminal(AST *ast) {
accept(ast);
}
virtual void node(AST *ast) {
out << _id[ast].constData() << " [label=\"" << name(ast).constData() << "\"];" << std::endl;
}
virtual bool preVisit(AST *ast) {
static int count = 1;
const QByteArray id = 'n' + QByteArray::number(count++);
_id[ast] = id;
if (! _stack.isEmpty())
_connections.append(qMakePair(_id[_stack.last()], id));
_stack.append(ast);
node(ast);
return true;
}
virtual void postVisit(AST *) {
_stack.removeLast();
}
private:
QHash<AST *, QByteArray> _id;
QList<QPair<QByteArray, QByteArray> > _connections;
QList<AST *> _stack;
QList<QByteArray> _terminalShapes;
std::ofstream out;
};
class SymbolDump: protected SymbolVisitor
{
public:
SymbolDump(TranslationUnit *unit)
: translationUnit(unit)
{
o.showArgumentNames = true;
o.showFunctionSignatures = true;
o.showReturnTypes = true;
}
void operator()(Symbol *s) {
QByteArray basename = translationUnit->fileName();
basename.append(".symbols.dot");
out.open(basename.constData());
out << "digraph Symbols { ordering=out;" << std::endl;
// std::cout << "rankdir = \"LR\";" << std::endl;
accept(s);
for (int i = 0; i < _connections.size(); ++i) {
QPair<Symbol*,Symbol*> connection = _connections.at(i);
QByteArray from = _id.value(connection.first);
if (from.isEmpty())
from = name(connection.first);
QByteArray to = _id.value(connection.second);
if (to.isEmpty())
to = name(connection.second);
out << from.constData() << " -> " << to.constData() << ";" << std::endl;
}
out << "}" << std::endl;
out.close();
}
protected:
QByteArray name(Symbol *s) {
#ifdef __GNUC__
QByteArray result = abi::__cxa_demangle(typeid(*s).name(), 0, 0, 0) + 11;
#else
QByteArray result = typeid(*s).name();
#endif
if (s->identifier()) {
result.append("\\nid: ");
result.append(s->identifier()->chars());
}
if (s->isDeprecated())
result.append("\\n(deprecated)");
return result;
}
virtual bool preVisit(Symbol *s) {
static int count = 0;
QByteArray nodeId("s");
nodeId.append(QByteArray::number(++count));
_id[s] = nodeId;
if (!_stack.isEmpty())
_connections.append(qMakePair(_stack.last(), s));
_stack.append(s);
return true;
}
virtual void postVisit(Symbol *) {
_stack.removeLast();
}
virtual void simpleNode(Symbol *symbol) {
out << _id[symbol].constData() << " [label=\"" << name(symbol).constData() << "\"];" << std::endl;
}
virtual bool visit(Class *symbol) {
const char *id = _id.value(symbol).constData();
out << id << " [label=\"";
if (symbol->isClass()) {
out << "class";
} else if (symbol->isStruct()) {
out << "struct";
} else if (symbol->isUnion()) {
out << "union";
} else {
out << "UNKNOWN";
}
out << "\\nid: ";
if (symbol->identifier()) {
out << symbol->identifier()->chars();
} else {
out << "NO ID";
}
if (symbol->isDeprecated())
out << "\\n(deprecated)";
out << "\"];" << std::endl;
return true;
}
virtual bool visit(UsingNamespaceDirective *symbol) { simpleNode(symbol); return true; }
virtual bool visit(UsingDeclaration *symbol) { simpleNode(symbol); return true; }
virtual bool visit(Declaration *symbol) {
out << _id[symbol].constData() << " [label=\"";
out << "Declaration\\n";
out << qPrintable(o(symbol->name()));
out << ": ";
out << qPrintable(o(symbol->type()));
if (symbol->isDeprecated())
out << "\\n(deprecated)";
if (Function *funTy = symbol->type()->asFunctionType()) {
if (funTy->isPureVirtual())
out << "\\n(pure virtual)";
else if (funTy->isVirtual())
out << "\\n(virtual)";
if (funTy->isSignal())
out << "\\n(signal)";
if (funTy->isSlot())
out << "\\n(slot)";
if (funTy->isInvokable())
out << "\\n(invokable)";
}
out << "\"];" << std::endl;
return true;
}
virtual bool visit(Argument *symbol) { simpleNode(symbol); return true; }
virtual bool visit(TypenameArgument *symbol) { simpleNode(symbol); return true; }
virtual bool visit(BaseClass *symbol) {
out << _id[symbol].constData() << " [label=\"BaseClass\\n";
out << qPrintable(o(symbol->name()));
if (symbol->isDeprecated())
out << "\\n(deprecated)";
out << "\"];" << std::endl;
return true;
}
virtual bool visit(Enum *symbol) { simpleNode(symbol); return true; }
virtual bool visit(Function *symbol) { simpleNode(symbol); return true; }
virtual bool visit(Namespace *symbol) { simpleNode(symbol); return true; }
virtual bool visit(Block *symbol) { simpleNode(symbol); return true; }
virtual bool visit(ForwardClassDeclaration *symbol) { simpleNode(symbol); return true; }
virtual bool visit(ObjCBaseClass *symbol) { simpleNode(symbol); return true; }
virtual bool visit(ObjCBaseProtocol *symbol) { simpleNode(symbol); return true; }
virtual bool visit(ObjCClass *symbol) { simpleNode(symbol); return true; }
virtual bool visit(ObjCForwardClassDeclaration *symbol) { simpleNode(symbol); return true; }
virtual bool visit(ObjCProtocol *symbol) { simpleNode(symbol); return true; }
virtual bool visit(ObjCForwardProtocolDeclaration *symbol) { simpleNode(symbol); return true; }
virtual bool visit(ObjCMethod *symbol) { simpleNode(symbol); return true; }
virtual bool visit(ObjCPropertyDeclaration *symbol) { simpleNode(symbol); return true; }
private:
TranslationUnit *translationUnit;
QHash<Symbol *, QByteArray> _id;
QList<QPair<Symbol *,Symbol*> >_connections;
QList<Symbol *> _stack;
std::ofstream out;
Overview o;
};
void createImageFromDot(const QString &inputFile, const QString &outputFile, bool verbose)
{
const QString command = CplusplusToolsUtils::portableExecutableName(QLatin1String("dot"));
const QStringList arguments = QStringList()
<< QLatin1String("-Tpng") << QLatin1String("-o") << outputFile << inputFile;
CplusplusToolsUtils::executeCommand(command, arguments, QString(), verbose);
}
const char PATH_STDIN_FILE[] = "_stdincontents.cpp";
QString example()
{
return
#if defined(Q_OS_WIN)
QString::fromLatin1("> echo int foo() {} | %1 && %2.ast.png")
#elif defined(Q_OS_MAC)
QString::fromLatin1("$ echo \"int foo() {}\" | ./%1 && open %2.ast.png")
#else
QString::fromLatin1("$ echo \"int foo() {}\" | ./%1 && xdg-open %2.ast.png")
#endif
.arg(QFileInfo(qApp->arguments().at(0)).fileName(), QLatin1String(PATH_STDIN_FILE));
}
void printUsage()
{
std::cout << "Usage: " << qPrintable(QFileInfo(qApp->arguments().at(0)).fileName())
<< " [-v] <file1> <file2> ...\n\n";
std::cout << QString::fromLatin1(
"Visualize AST and symbol hierarchy of given C++ files by generating png image files\n"
"in the same directory as the input files. Print paths to generated image files.\n"
"\n"
"Standard input is also read. The resulting files starts with \"%1\"\n"
"and are created in the current working directory. To show the AST for simple snippets\n"
"you might want to execute:\n"
"\n"
" %2\n"
"\n"
"Prerequisites:\n"
" 1) Make sure to have 'dot' from graphviz locatable by PATH.\n"
" 2) Make sure to have an up to date dumpers file by using 'cplusplus-update-frontend'.\n"
).arg(QLatin1String(PATH_STDIN_FILE), example()).toLocal8Bit().constData();
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = app.arguments();
args.removeFirst();
bool optionVerbose = false;
// Data from stdin?
if (!tty_for_stdin()) {
QFile file((QLatin1String(PATH_STDIN_FILE)));
if (! file.open(QFile::WriteOnly)) {
std::cerr << "Error: Cannot open file for writing\"" << qPrintable(file.fileName())
<< "\"" << std::endl;
exit(EXIT_FAILURE);
}
file.write(QTextStream(stdin).readAll().toLocal8Bit());
file.close();
args.append(file.fileName());
}
// Process options & arguments
if (args.contains(QLatin1String("-v"))) {
optionVerbose = true;
args.removeOne(QLatin1String("-v"));
}
const bool helpRequested = args.contains(QLatin1String("-h"))
|| args.contains(QLatin1String("-help"));
if (args.isEmpty() || helpRequested) {
printUsage();
return helpRequested ? EXIT_SUCCESS : EXIT_FAILURE;
}
// Process files
const QStringList files = args;
foreach (const QString &fileName, files) {
if (! QFile::exists(fileName)) {
std::cerr << "Error: File \"" << qPrintable(fileName) << "\" does not exist."
<< std::endl;
exit(EXIT_FAILURE);
}
// Run the preprocessor
const QString fileNamePreprocessed = fileName + QLatin1String(".preprocessed");
CplusplusToolsUtils::SystemPreprocessor preprocessor(optionVerbose);
preprocessor.preprocessFile(fileName, fileNamePreprocessed);
// Convert to dot
QFile file(fileNamePreprocessed);
if (! file.open(QFile::ReadOnly)) {
std::cerr << "Error: Could not open file \"" << qPrintable(fileNamePreprocessed)
<< "\"" << std::endl;
exit(EXIT_FAILURE);
}
const QByteArray source = file.readAll();
file.close();
Document::Ptr doc = Document::create(fileName);
doc->control()->setDiagnosticClient(0);
doc->setUtf8Source(source);
doc->parse();
doc->check();
ASTDump dump(doc->translationUnit());
dump(doc->translationUnit()->ast());
SymbolDump dump2(doc->translationUnit());
dump2(doc->globalNamespace());
// Create images
typedef QPair<QString, QString> Pair;
QList<Pair> inputOutputFiles;
inputOutputFiles.append(qMakePair(QString(fileName + QLatin1String(".ast.dot")),
QString(fileName + QLatin1String(".ast.png"))));
inputOutputFiles.append(qMakePair(QString(fileName + QLatin1String(".symbols.dot")),
QString(fileName + QLatin1String(".symbols.png"))));
foreach (const Pair &pair, inputOutputFiles) {
createImageFromDot(pair.first, pair.second, optionVerbose);
std::cout << qPrintable(QDir::toNativeSeparators(pair.second)) << std::endl;
}
}
return EXIT_SUCCESS;
}