Задача: с помощью 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
А можно посмотреть, что в итоге получилось, сам xml файл?
ОтветитьУдалитьМожно.
ОтветитьУдалитьhttp://code.google.com/p/white-knight-is-alive/source/browse/trunk/examples/clang/ast.xml
Заодно добавил ссылку в конце поста.
Спасибо! А то хотелось посмотреть как же этот AST в xml выглядит.
ОтветитьУдалитьМожно получить исходники?
ОтветитьУдалитьhttp://code.google.com/p/white-knight-is-alive/source/browse/trunk/examples/clang/
УдалитьПытаюсь скомпилировать с clang3.0 не выходит. Какую версию библиотеки использовали?
УдалитьВ тексте же написано :)
Удалить"Я буду использовать clang версии 2.8"
Простите не заметил. Большое спасибо за статью :)
УдалитьПожалуйста :)
УдалитьЕще вопрос
Удалитьдля функций тоже будет вывод в формате XML как для методов или в таком же формате как выдает -ast-print-xml например:
(CompoundStmt 0x26c7d350
(ReturnStmt 0x26c7d340
(CallExpr 0x26c7d2f8 'long long'
(ImplicitCastExpr 0x26c7d2e8 'long long (*)(int, long long, int) __attribute__((cdecl))'
(DeclRefExpr 0x26c7d214 'long long (int, long long, int) __attribute__((cdecl))' Function 0x26c7fc50 '_lseeki64' 'long long (int, long long, int) __attribute__((cdecl))'))
(ImplicitCastExpr 0x26c7d320 'int'
(DeclRefExpr 0x26c7d22c 'int' lvalue ParmVar 0x26c80050 'fd' 'int'))
(CStyleCastExpr 0x26c7d280 'long long'
(ImplicitCastExpr 0x26c7d270 'off64_t':'long long'
(DeclRefExpr 0x26c7d244 'off64_t':'long long' lvalue ParmVar 0x26c80090 'offset' 'off64_t':'long long')))
(ImplicitCastExpr 0x26c7d330 'int'
(DeclRefExpr 0x26c7d298 'int' lvalue ParmVar 0x26c7d0e0 'whence' 'int')))))
Я, к сожалению, не знаю.
УдалитьЕсть скомпилированная версия? Не получилось скомпилировать(
УдалитьДобавил UPD2 в помощь.
ОтветитьУдалить