Исходный файл такой же, как и в предыдущем примере. Взять его можно здесь. Используется Clang версии 2.8.
Clang предоставляет набор базовых классов, реализующих механизм доступа к узлам дерева. Сам механизм довольно простой — нужно определить класс-потребитель, унаследованный от абстрактного интерфейса
clang::ASTConsumer
, и имплементировать метод HandleTranslationUnit
, который будет вызван при завершении анализа входного файла. В этот метод будет передан объект контекста, содержащий ссылку на корневой узел дерева. Для самостоятельного обхода дерева нужно создать собственную имплементацию паттерна Visitor. Программист должен просто унаследовать свой класс от базового Visitor'а, предоставляемого clang'ом, и переопределить виртуальные функции, ответственные за обработку тех или иных типов узлов синтаксического дерева.В двух словах порядок действий:
- Создать экземпляр класса
clang::Preprocessor
, который инкапсулирует в себя функциональность, связанную с анализом исходного файла. Задать входной файл для анализа. - Создать экземпляр класса
clang::ASTContext
, который хранит узлы дерева. - Создать экземпляр своего собственного класса-потребителя, имплементирующий обход дерева.
- Вызвать функцию
clang::ParseAST()
, которая выполнит разбор входного файла с помощьюPreprocessor
, построит AST, положит его вASTContext
и вызовет функциюHandleTranslationUnit
класса-потребителя для обработки дерева.
Создание и инициализация экземпляра Preprocessor
Для создания экземпляра классаPreprocessor
нам потребуются экземпляры классов Diagnostic
, LangOptions
, TargetInfo
, SourceManager
и HeaderSearch
.Создадим экземпляр класса диагностики, использующий для вывода текстовый буфер. Также создадим и инициализируем классы
LangOptions
и TargetInfo
:Diagnostic diagnostic(new TextDiagnosticBuffer);
LangOptions langOptions;
langOptions.CPlusPlus = 1; // We will parse a C++ file.
TargetOptions targetOptions;
targetOptions.Triple = "x86_64"; // x86, ppc, ...
auto_ptr<TargetInfo> targetInfo(
TargetInfo::CreateTargetInfo(diagnostic, targetOptions));
assert(0 == diagnostic.getNumErrors());
assert(0 != targetInfo.get());
LangOptions langOptions;
langOptions.CPlusPlus = 1; // We will parse a C++ file.
TargetOptions targetOptions;
targetOptions.Triple = "x86_64"; // x86, ppc, ...
auto_ptr<TargetInfo> targetInfo(
TargetInfo::CreateTargetInfo(diagnostic, targetOptions));
assert(0 == diagnostic.getNumErrors());
assert(0 != targetInfo.get());
Теперь создадим экземпляры классов
SourceManager
и HeaderSearch
. SourceManager
— класс, отвечающий за поиск и буферизацию входных файлов для анализа. HeaderSearch
обеспечивает поиск заголовочных файлов:SourceManager sourceManager(diagnostic);
FileManager fileManager;
HeaderSearch headerSearch(fileManager);
FileManager fileManager;
HeaderSearch headerSearch(fileManager);
Теперь можно создать экземпляр класса-препроцессора:
Preprocessor preprocessor(diagnostic,
langOptions,
*targetInfo,
sourceManager,
headerSearch);
langOptions,
*targetInfo,
sourceManager,
headerSearch);
Зададим входной файл для анализа:
const FileEntry* file = fileManager.getFile("a.cpp");
assert(0 != file);
sourceManager.createMainFileID(file);
assert(0 != file);
sourceManager.createMainFileID(file);
Создание экземпляра ASTContext
Экземпляр класса контекста создается очень просто. Описание используемых классов можно посмотреть в заголовочных файлах clang'а.IdentifierTable identifierTable(langOptions);
SelectorTable selectorTable;
clang::Builtin::Context builtinContext(*targetInfo);
const unsigned int NumReserved = 10; // зарезервированный размер
ASTContext astContext(langOptions,
sourceManager,
*targetInfo,
identifierTable,
selectorTable,
builtinContext,
NumReserved);
SelectorTable selectorTable;
clang::Builtin::Context builtinContext(*targetInfo);
const unsigned int NumReserved = 10; // зарезервированный размер
ASTContext astContext(langOptions,
sourceManager,
*targetInfo,
identifierTable,
selectorTable,
builtinContext,
NumReserved);
Реализация класса-потребителя и класса-визитёра
Итак, теперь нужно сделать основную работу — определить класс-потребитель и имплементировать механизм обхода дерева.Реализация класса-потребителя будет простой, потому что нам нужно имплементировать всего одну функцию
HandleTranslationUnit
. В ней мы берем корневой элемент дерева (TranslationUnit) и "посещаем" его с помощью класса-визитёра, который мы имплементируем чуть позже:class MyAstConsumer : public ASTConsumer
{
public:
virtual void HandleTranslationUnit (ASTContext &ctx)
{
TranslationUnitDecl *translationUnit = ctx.getTranslationUnitDecl();
MyDeclVisitor().Visit(translationUnit);
}
};
{
public:
virtual void HandleTranslationUnit (ASTContext &ctx)
{
TranslationUnitDecl *translationUnit = ctx.getTranslationUnitDecl();
MyDeclVisitor().Visit(translationUnit);
}
};
Класс-посетитель немного сложнее в реализации. В первую очередь необходимо реализовать обработчик посещения узла
TranslationUnitDecl
. Затем нужно реализовать обход всех его дочерних узлов. Для нашего удобства любой узел, который содержит дочерние узлы, может быть обработан как DeclContext
, поэтому далее нужно реализовать обработчик для типа узла DeclContext
и вызвать эту функцию, передав в нее наш translation unit. В этом обработчике выполняется "посещение" всех дочерних узлов, поэтому для каждого интересующего нас типа узла в классе-визитёре должен быть создан соответствующий обработчик (VisitCXXRecordDecl
, VisitFieldDecl
, VisitCXXMethod
, ...). В нашей реализации класс-визитёр выводит в stderr
названия типов узлов, которые он посещает: class MyDeclVisitor : public clang::DeclVisitor<MyDeclVisitor>
{
llvm::raw_ostream &_out;
public:
MyDeclVisitor () : _out(llvm::errs())
{
}
void VisitTranslationUnitDecl (clang::TranslationUnitDecl *D)
{
_out << D->Decl::getDeclKindName() << "\n";
// A translation unit has child nodes, so consider that as a context.
VisitDeclContext(D);
}
void VisitDeclContext (clang::DeclContext *DC)
{
// NOTE: The lines below were taken from Clang source code.
// This code tries to skip any service information which is
// unnecessary for an end-user in this case.
clang::DeclContext::decl_iterator DBegin = DC->decls_begin();
clang::DeclContext::decl_iterator DEnd = DC->decls_end();
for (clang::DeclContext::decl_iterator i = DBegin; i != DEnd; ++i)
{
// Skip over implicit declarations
if ((*i)->isImplicit())
{
continue;
}
if (clang::NamedDecl *ND = llvm::dyn_cast<clang::NamedDecl>(*i))
{
if (clang::IdentifierInfo *II = ND->getIdentifier())
{
if (II->isStr("__builtin_va_list")
|| II->isStr("__int128_t")
|| II->isStr("__uint128_t"))
{
continue;
}
}
}
_out << __FUNCTION__ << ": " << (*i)->getDeclKindName() << "\n";
Visit(*i); // Visit all the child nodes.
}
}
void VisitCXXRecordDecl (clang::CXXRecordDecl *D)
{
// process c++ class
_out << "CXXRecord: " << D->getKindName();
if (D->getIdentifier())
{
_out << ' ' << D;
}
_out << "\n";
// A class has child nodes, so consider that as a context.
VisitDeclContext(D);
}
void VisitFieldDecl (clang::FieldDecl *D)
{
// process a field here
}
void VisitCXXConstructorDecl (clang::CXXConstructorDecl *D)
{
// process a class constructor here
}
void VisitCXXMethodDecl (clang::CXXMethodDecl *D)
{
// process a class method here
}
};
{
llvm::raw_ostream &_out;
public:
MyDeclVisitor () : _out(llvm::errs())
{
}
void VisitTranslationUnitDecl (clang::TranslationUnitDecl *D)
{
_out << D->Decl::getDeclKindName() << "\n";
// A translation unit has child nodes, so consider that as a context.
VisitDeclContext(D);
}
void VisitDeclContext (clang::DeclContext *DC)
{
// NOTE: The lines below were taken from Clang source code.
// This code tries to skip any service information which is
// unnecessary for an end-user in this case.
clang::DeclContext::decl_iterator DBegin = DC->decls_begin();
clang::DeclContext::decl_iterator DEnd = DC->decls_end();
for (clang::DeclContext::decl_iterator i = DBegin; i != DEnd; ++i)
{
// Skip over implicit declarations
if ((*i)->isImplicit())
{
continue;
}
if (clang::NamedDecl *ND = llvm::dyn_cast<clang::NamedDecl>(*i))
{
if (clang::IdentifierInfo *II = ND->getIdentifier())
{
if (II->isStr("__builtin_va_list")
|| II->isStr("__int128_t")
|| II->isStr("__uint128_t"))
{
continue;
}
}
}
_out << __FUNCTION__ << ": " << (*i)->getDeclKindName() << "\n";
Visit(*i); // Visit all the child nodes.
}
}
void VisitCXXRecordDecl (clang::CXXRecordDecl *D)
{
// process c++ class
_out << "CXXRecord: " << D->getKindName();
if (D->getIdentifier())
{
_out << ' ' << D;
}
_out << "\n";
// A class has child nodes, so consider that as a context.
VisitDeclContext(D);
}
void VisitFieldDecl (clang::FieldDecl *D)
{
// process a field here
}
void VisitCXXConstructorDecl (clang::CXXConstructorDecl *D)
{
// process a class constructor here
}
void VisitCXXMethodDecl (clang::CXXMethodDecl *D)
{
// process a class method here
}
};
Выполнение анализа файла и обход дерева
Теперь необходимо создать экземпляр класса-потребителя и вызвать функциюParseAST
:MyAstConsumer consumer;
clang::ParseAST(preprocessor, &consumer, astContext);
assert(0 == diagnostic.getNumErrors());
clang::ParseAST(preprocessor, &consumer, astContext);
assert(0 == diagnostic.getNumErrors());
После выполнения программы в
stderr
должно быть выведено что-то вроде:TranslationUnit VisitDeclContext: CXXRecord CXXRecord: struct MyAnotherClass VisitDeclContext: Field VisitDeclContext: Field VisitDeclContext: CXXConstructor VisitDeclContext: CXXMethod
Обход дерева успешно завершен!
Исходный код (немного расширенный и дополненный) для этой и для прошлой статьи можно взять здесь.
А каким образом можно скомпилировать данный файл?
ОтветитьУдалитьДумаю, что компилятором :) Попробуйте gcc.
УдалитьНа всякий случай проверил - при компиляции GСС особых проблем нет для любой версии от 4.4 до 4.6. А вот линковать лучше gcc-4.4.
УдалитьВидимо я неправильно сформулировал вопрос. Меня интересовало с какими аргументами необходимо вызывать компилятор.
УдалитьДавайте попробуем с другой стороны :)
УдалитьС какими аргументами запускаете Вы и в чем у Вас возникает проблема?
Подскажите, пожалуйста. Пытаюсь освоить DeclVisitor по вашей статье и столкнулся с проблемой. При компиляции я получаю простыню ошибок вида:
ОтветитьУдалить/usr/local/include/clang/AST/DeclNodes.inc: In member function 'RetTy clang::DeclVisitor::Visit(clang::Decl*) [with ImplClass = clang::MyDeclVisitor, RetTy = void]':
DeclVisitor.cpp:48: instantiated from here
/usr/local/include/clang/AST/DeclNodes.inc:15: error: 'clang::DeclVisitor' is an inaccessible base of 'clang::MyDeclVisitor'
/usr/local/include/clang/AST/DeclNodes.inc:15: error: return-statement with a value, in function returning 'void'
/usr/local/include/clang/AST/DeclNodes.inc:21: error: 'clang::DeclVisitor' is an inaccessible base of 'clang::MyDeclVisitor'
/usr/local/include/clang/AST/DeclNodes.inc:21: error: return-statement with a value, in function returning 'void'
/usr/local/include/clang/AST/DeclNodes.inc:27: error: 'clang::DeclVisitor' is an inaccessible base of 'clang::MyDeclVisitor'
/usr/local/include/clang/AST/DeclNodes.inc:27: error: return-statement with a value, in function returning 'void'
При этом, если закомментировать вызов Visit(*i) в функции VisitDeclContext, то программа компилируется без ошибок, но проход осуществляется только по декларациям верхнего уровня.
Не подскажете почему возникает такая ситуация?
Скорее всего у Вас другая версия библиотеки, не та, которой пользовался я. Вам нужно разобраться в семантике методов используемых классов и реализовать наследников в соответствии.
УдалитьЯ честно разбирался, а потом спросил :)
УдалитьВ итоге воспользовался другими возможностями API для решения той же задачи.