Задача: с помощью Clang API получить XML-файл, описывающий абстрактное синтаксическое дерево (AST) исходного кода. Это можно сделать и простым запуском команды
clang
с параметром -ast-print-xml
в командной строке, но через API — интереснее :)Библиотеки clang'а еще не входят в официальные дистрибутивы, поэтому первое, что нужно сделать — выгрузить clang из репозитория и собрать. Clang хорош тем, что собрать его можно и для Unix-подобных систем и для Windows. Я буду использовать clang версии 2.8, так как на данный момент она является последней стабильной версией. К сожалению, clang собирается только как часть процесса сборки LLVM, поэтому, если в системе уже установлен LLVM, то его всё равно придется пересобирать.
Теперь создадим тестовый файл с исходным кодом на C++, для которого будем строить AST, назовем его
a.cpp
и положим куда-нибудь недалеко. Вот он (файл можно взять здесь):- struct MyClass
- {
- unsigned long _a;
- mutable unsigned long _b;
- MyClass () : _a(4), _b(44) {}
- unsigned long func ()
- {
- unsigned long r = _a + _b;
- return r;
- }
- };
Использование параметров командной строки
Первая сущность, с которой предстоит познакомиться, этоCompilerInstance
— класс, который является фронтэндом компилятора и выполняет всю "грязную" и "неинтересную" работу. Он находится в пространстве имен clang
и создаётся просто:auto_ptr<CompilerInstance> compiler(new CompilerInstance);
Затем нужно создать экземпляр класса
Diagnostic
, который будет использоваться компилятором для сбора информации об ошибках и проблемах. Сейчас нас устроит класс, работающий с stdout
, поэтому пусть компилятор создаст его сам:compiler->createDiagnostics(0, NULL); // to stdout
assert(compiler->hasDiagnostics());
assert(compiler->hasDiagnostics());
Компилятор есть, можно начинать с ним работать — нужно передать в компилятор входные параметры командной строки, необходимые для его инициализации:
const char *args[] =
{
"-cc1", // вызов LLVM Clang
"a.cpp" // входной файл
};
clang::CompilerInvocation::CreateFromArgs(
compiler->getInvocation(),
args,
args + 2,
compiler->getDiagnostics());
assert(0 == compiler->getDiagnostics().getNumErrors());
{
"-cc1", // вызов LLVM Clang
"a.cpp" // входной файл
};
clang::CompilerInvocation::CreateFromArgs(
compiler->getInvocation(),
args,
args + 2,
compiler->getDiagnostics());
assert(0 == compiler->getDiagnostics().getNumErrors());
Теперь нужно создать класс-действие и передать его компилятору на запуск. Так как мы хотим сгенерировать AST в виде XML-файла, то нужно создать экземпляр класса
ASTPrintXMLAction
, который является наследником класса FrontendAction
, и затем выполнить действие с помощью метода CompilerInstance::ExecuteAction()
:auto_ptr<FrontendAction> action(new ASTPrintXMLAction);
bool actionSuccessful = compiler->ExecuteAction(*action);
assert(0 == compiler->getDiagnostics().getNumErrors());
assert(actionSuccessful);
bool actionSuccessful = compiler->ExecuteAction(*action);
assert(0 == compiler->getDiagnostics().getNumErrors());
assert(actionSuccessful);
Всё, что нужно, уже написано. Теперь программу нужно скомпилировать, слинковать с библиотеками clang и LLVM и запустить.
Если всё прошло успешно, то в директории, где мы запускаем исполняемый файл, появится файл
a.xml
, содержащий AST файла a.cpp
.Инициализация фронтэнда вручную
Теперь попробуем не использовать методCompilerInvocation::CreateFromArgs()
, а инициализировать фронтэнд компилятора самостоятельно (будем использовать минимальный набор параметров для успешного генерирования AST). Начало такое же, как и в предыдущем примере. Создаём компилятор и диагностику:auto_ptr<CompilerInstance> compiler(new CompilerInstance);
compiler->createDiagnostics(0, NULL); // to stdout
assert(compiler->hasDiagnostics());
compiler->createDiagnostics(0, NULL); // to stdout
assert(compiler->hasDiagnostics());
Зададим входной файл, ну и выходной за компанию:
pair<InputKind, string> input(IK_CXX, "a.cpp");
compiler->getFrontendOpts().Inputs.push_back(input);
compiler->getFrontendOpts().OutputFile = "ast.xml";
compiler->getFrontendOpts().Inputs.push_back(input);
compiler->getFrontendOpts().OutputFile = "ast.xml";
Теперь нужно указать платформу, а то без нее никуда, и указать язык исходного кода, так как указания типа входного файла
IK_CXX
недостаточно (в данном случае там вообще можно указать IK_None
):compiler->getTargetOpts().Triple = "x86_64"; // x86, alpha, ppc, ppc64, ...
compiler->getLangOpts().CPlusPlus = 1;
compiler->getLangOpts().CPlusPlus = 1;
Теперь создаем экземпляр класса-действия и выполняем его:
auto_ptr<FrontendAction> action(new ASTPrintXMLAction);
bool actionSuccessful = compiler->ExecuteAction(*action);
assert(0 == compiler->getDiagnostics().getNumErrors());
assert(actionSuccessful);
bool actionSuccessful = compiler->ExecuteAction(*action);
assert(0 == compiler->getDiagnostics().getNumErrors());
assert(actionSuccessful);
После сборки и запуска программы появится файл
ast.xml
, который должен совпадать по содержимому с файлом a.xml
, который у нас получился в прошлом примере.UPD: Получившийся XML-файл можно взять здесь.
Исходный код для тестов - здесь.
UPD2: После того, как вы соберете llvm и clang (и выполните make install
), у вас появится исполняемый файл llvm-config, который вам очень поможет при компиляции и линковке ваших собственных проектов.
- Директории с заголовочными файлами:
llvm-config --includedir
- С/C++ флаги:
llvm-config --cppflags | --cflags | --cxxflags
- LD флаги:
llvm-config --ldflags
- Либы:
llvm-config --libs
(только llvm-либы, либы clang вам придется прописывать руками) - И многое другое:
llvm-config --help