-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
928 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,3 +30,12 @@ | |
*.exe | ||
*.out | ||
*.app | ||
|
||
# CMake | ||
cmake-build-debug | ||
|
||
# Echo | ||
*.echo.h | ||
*.echo.hh | ||
*.echo.hpp | ||
*.echo.hxx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
cmake_minimum_required(VERSION 3.12) | ||
project(echo) | ||
|
||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) | ||
|
||
set(CMAKE_CXX_COMPILER "clang++-7") | ||
set(CMAKE_CXX_STANDARD 17) | ||
set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-unused-parameter") | ||
|
||
add_subdirectory(generator) | ||
add_subdirectory(library) | ||
add_subdirectory(test) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
# echo | ||
A tool to generate reflection data in C++ | ||
A tool to generate reflection data in C++ using Clang/LLVM. | ||
|
||
Toy project, (heavily) inspired by [Arvid Gerstmann's talk at CppCon 2018](https://www.youtube.com/watch?v=DUiUBt-fqEY&index=131). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
add_executable(echogen | ||
${CMAKE_CURRENT_SOURCE_DIR}/src/annotated_match_callback.hpp | ||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp | ||
) | ||
|
||
set(LLVM_PATH /usr/lib/llvm-7) | ||
|
||
target_include_directories(echogen PRIVATE ${LLVM_PATH}/include) | ||
target_link_directories(echogen PRIVATE ${LLVM_PATH}/lib) | ||
|
||
target_link_libraries(echogen | ||
PRIVATE | ||
stdc++fs | ||
LLVM-7 | ||
clangTooling | ||
clangFrontendTool | ||
clangFrontend | ||
clangDriver | ||
clangSerialization | ||
clangParse | ||
clangSema | ||
clangAnalysis | ||
clangEdit | ||
clangAST | ||
clangASTMatchers | ||
clangLex | ||
clangBasic | ||
clang | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
/* | ||
** Created by doom on 13/02/19. | ||
*/ | ||
|
||
#ifndef ECHOGEN_ANNOTATED_MATCH_CALLBACK_HPP | ||
#define ECHOGEN_ANNOTATED_MATCH_CALLBACK_HPP | ||
|
||
#include <iostream> | ||
#include <fstream> | ||
#include <filesystem> | ||
#include <cctype> | ||
#include <clang/ASTMatchers/ASTMatchFinder.h> | ||
#include <clang/ASTMatchers/ASTMatchers.h> | ||
|
||
namespace echogen | ||
{ | ||
class annotated_field | ||
{ | ||
private: | ||
const clang::CXXRecordDecl *record_; | ||
const clang::FieldDecl *field_; | ||
|
||
public: | ||
annotated_field(const clang::CXXRecordDecl *record, const clang::FieldDecl *field) noexcept : | ||
record_(record), field_(field) | ||
{ | ||
} | ||
|
||
void dump_field_descriptor(llvm::raw_ostream &os) const noexcept | ||
{ | ||
os << " {\n"; | ||
os << " get_type<"; | ||
field_->getType().print(os, field_->getASTContext().getPrintingPolicy()); | ||
os << ">(),\n"; | ||
os << " \"" << field_->getName() << "\",\n"; | ||
os << " offsetof(" << record_->getName() << ", " << field_->getName() << "),\n"; | ||
os << " },\n"; | ||
} | ||
}; | ||
|
||
class annotated_function | ||
{ | ||
private: | ||
const clang::CXXRecordDecl *record_; | ||
const clang::FunctionDecl *function_; | ||
|
||
public: | ||
annotated_function(const clang::CXXRecordDecl *record, const clang::FunctionDecl *function) noexcept : | ||
record_(record), function_(function) | ||
{ | ||
} | ||
|
||
void dump_parameters_descriptors(llvm::raw_ostream &os) const noexcept | ||
{ | ||
os << " static const field_descriptor " << function_->getName() << "_params[] = {\n"; | ||
for (auto param_it = function_->param_begin(); param_it != function_->param_end(); ++param_it) { | ||
os << " {\n"; | ||
os << " get_type<"; | ||
(*param_it)->getType().print(os, (*param_it)->getASTContext().getPrintingPolicy()); | ||
os << ">(),\n"; | ||
os << " \"" << (*param_it)->getName() << "\",\n"; | ||
os << " 0,\n"; | ||
os << " },\n"; | ||
} | ||
os << " };\n"; | ||
} | ||
|
||
void dump_function_descriptor(llvm::raw_ostream &os) const noexcept | ||
{ | ||
os << " {\n"; | ||
os << " get_type<"; | ||
function_->getReturnType().print(os, function_->getASTContext().getPrintingPolicy()); | ||
os << ">(),\n"; | ||
os << " \"" << function_->getName() << "\",\n"; | ||
os << " {" << function_->getName() << "_params, sizeof(" | ||
<< function_->getName() << "_params) / sizeof(" | ||
<< function_->getName() << "_params[0])},\n"; | ||
os << " },\n"; | ||
} | ||
}; | ||
|
||
class annotated_class | ||
{ | ||
private: | ||
const clang::CXXRecordDecl *record_; | ||
std::vector<annotated_field> fields_; | ||
std::vector<annotated_function> functions_; | ||
|
||
public: | ||
explicit annotated_class(const clang::CXXRecordDecl *record) noexcept : record_(record) | ||
{ | ||
} | ||
|
||
void add_annotated_field(const clang::FieldDecl *field) noexcept | ||
{ | ||
fields_.emplace_back(record_, field); | ||
} | ||
|
||
void add_annotated_function(const clang::FunctionDecl *function) noexcept | ||
{ | ||
functions_.emplace_back(record_, function); | ||
} | ||
|
||
void dump_reflection_data(llvm::raw_ostream &os) const noexcept | ||
{ | ||
os << "namespace echo\n"; | ||
os << "{\n"; | ||
os << " template <>\n"; | ||
os << " inline const class_descriptor &get_class<" << record_->getName() << ">() noexcept\n"; | ||
os << " {\n"; | ||
|
||
if (not fields_.empty()) { | ||
os << " static const field_descriptor fields[] = {\n"; | ||
for (const auto &f : fields_) { | ||
f.dump_field_descriptor(os); | ||
} | ||
os << " };\n"; | ||
} | ||
|
||
if (not functions_.empty()) { | ||
for (const auto &f : functions_) { | ||
f.dump_parameters_descriptors(os); | ||
} | ||
os << " static const function_descriptor functions[] = {\n"; | ||
for (const auto &f : functions_) { | ||
f.dump_function_descriptor(os); | ||
} | ||
os << " };\n"; | ||
} | ||
|
||
os << " static const class_descriptor desc{\n"; | ||
os << " type_descriptor{\n"; | ||
os << " \"" << record_->getName() << "\",\n"; | ||
os << " sizeof(" << record_->getName() << "),\n"; | ||
os << " type_tags::class_tag,\n"; | ||
os << " details::hash(\"" << record_->getName() << "\"),\n"; | ||
os << " },\n"; | ||
if (not fields_.empty()) { | ||
os << " {fields, sizeof(fields) / sizeof(fields[0])},\n"; | ||
} else { | ||
os << " {},\n"; | ||
} | ||
if (not functions_.empty()) { | ||
os << " {functions, sizeof(functions) / sizeof(functions[0])},\n"; | ||
} else { | ||
os << " {},\n"; | ||
} | ||
os << " };\n"; | ||
os << "\n"; | ||
os << " return desc;\n"; | ||
os << " }\n"; | ||
os << "\n"; | ||
|
||
os << " template <>\n"; | ||
os << " inline const type_descriptor &get_type<" << record_->getName() << ">() noexcept\n"; | ||
os << " {\n"; | ||
os << " return get_class<" << record_->getName() << ">();\n"; | ||
os << " }\n"; | ||
|
||
os << "}\n\n"; | ||
} | ||
}; | ||
|
||
class annotated_match_callback : public clang::ast_matchers::MatchFinder::MatchCallback | ||
{ | ||
private: | ||
std::optional<std::filesystem::path> out_path_; | ||
std::vector<annotated_class> classes_; | ||
|
||
void handle_record_decl(const clang::CXXRecordDecl *record) noexcept | ||
{ | ||
classes_.emplace_back(record); | ||
} | ||
|
||
void handle_field_decl(const clang::FieldDecl *field) | ||
{ | ||
assert(not classes_.empty()); | ||
classes_.back().add_annotated_field(field); | ||
} | ||
|
||
void handle_function_decl(const clang::FunctionDecl *function) | ||
{ | ||
assert(not classes_.empty()); | ||
classes_.back().add_annotated_function(function); | ||
} | ||
|
||
void set_file_path(const clang::Decl *decl, const clang::SourceManager *source_manager) noexcept | ||
{ | ||
if (not out_path_) { | ||
auto loc = decl->getSourceRange().getBegin(); | ||
auto name = source_manager->getFilename(loc); | ||
std::filesystem::path path = (std::string)name; | ||
path.replace_filename(path.filename().stem().string() + ".echo" + path.extension().string()); | ||
out_path_ = path; | ||
} | ||
} | ||
|
||
public: | ||
void run(const clang::ast_matchers::MatchFinder::MatchResult &result) override | ||
{ | ||
if (auto record = result.Nodes.getNodeAs<clang::CXXRecordDecl>("id"); record) { | ||
set_file_path(record, result.SourceManager); | ||
handle_record_decl(record); | ||
} else if (auto field = result.Nodes.getNodeAs<clang::FieldDecl>("id"); field) { | ||
handle_field_decl(field); | ||
} else if (auto function = result.Nodes.getNodeAs<clang::FunctionDecl>("id"); function) { | ||
handle_function_decl(function); | ||
} | ||
} | ||
|
||
void onEndOfTranslationUnit() override | ||
{ | ||
if (not out_path_) { | ||
llvm::errs() << "no echo'ed structure or class was found\n"; | ||
return; | ||
} | ||
llvm::outs() << "generating echo'ed data for " << *out_path_ << "\n"; | ||
|
||
std::error_code ec; | ||
llvm::raw_fd_ostream ofs((*out_path_).string(), ec); | ||
assert(!ec); | ||
|
||
std::string guard_macro = out_path_->filename().string(); | ||
std::transform(guard_macro.begin(), guard_macro.end(), guard_macro.begin(), [](auto &&c) { | ||
return std::isalnum(c) ? std::toupper(c) : '_'; | ||
}); | ||
|
||
ofs << "#ifndef " << guard_macro << "\n"; | ||
ofs << "#define " << guard_macro << "\n\n"; | ||
|
||
for (const auto &cls : classes_) { | ||
cls.dump_reflection_data(ofs); | ||
} | ||
|
||
ofs << "#endif /* !" << guard_macro << " */\n"; | ||
|
||
out_path_.reset(); | ||
classes_.clear(); | ||
} | ||
}; | ||
} | ||
|
||
#endif /* !ECHOGEN_ANNOTATED_MATCH_CALLBACK_HPP */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
** Created by doom on 11/02/19. | ||
*/ | ||
|
||
#include <clang/Tooling/CommonOptionsParser.h> | ||
#include <clang/Tooling/Tooling.h> | ||
#include <clang/Frontend/FrontendActions.h> | ||
#include <clang/Frontend/ASTConsumers.h> | ||
#include <clang/ASTMatchers/ASTMatchFinder.h> | ||
|
||
#include "annotated_match_callback.hpp" | ||
|
||
int main(int ac, const char **av) | ||
{ | ||
using namespace clang::tooling; | ||
using namespace clang::ast_matchers; | ||
|
||
llvm::cl::OptionCategory category("echogen options"); | ||
CommonOptionsParser opt_parser(ac, av, category); | ||
ClangTool tool(opt_parser.getCompilations(), opt_parser.getSourcePathList()); | ||
|
||
MatchFinder mf; | ||
|
||
DeclarationMatcher class_matcher = cxxRecordDecl(decl().bind("id"), hasAttr(clang::attr::Annotate)); | ||
DeclarationMatcher field_matcher = fieldDecl(decl().bind("id"), hasAttr(clang::attr::Annotate)); | ||
DeclarationMatcher function_matcher = functionDecl(decl().bind("id"), hasAttr(clang::attr::Annotate)); | ||
|
||
echogen::annotated_match_callback match_cb; | ||
|
||
mf.addMatcher(class_matcher, &match_cb); | ||
mf.addMatcher(field_matcher, &match_cb); | ||
mf.addMatcher(function_matcher, &match_cb); | ||
|
||
return tool.run(newFrontendActionFactory(&mf).get()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
add_library(echo INTERFACE) | ||
|
||
target_sources(echo INTERFACE | ||
${CMAKE_CURRENT_SOURCE_DIR}/include/echo/details/array_view.hpp | ||
${CMAKE_CURRENT_SOURCE_DIR}/include/echo/details/string_view_hash.hpp | ||
${CMAKE_CURRENT_SOURCE_DIR}/include/echo/debug.hpp | ||
${CMAKE_CURRENT_SOURCE_DIR}/include/echo/echo.hpp | ||
) | ||
|
||
target_include_directories(echo INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) |
Oops, something went wrong.