Skip to content

Commit

Permalink
Add a first draft
Browse files Browse the repository at this point in the history
  • Loading branch information
doom committed Feb 16, 2019
1 parent 1b39c71 commit 12fde08
Show file tree
Hide file tree
Showing 16 changed files with 928 additions and 1 deletion.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@
*.exe
*.out
*.app

# CMake
cmake-build-debug

# Echo
*.echo.h
*.echo.hh
*.echo.hpp
*.echo.hxx
12 changes: 12 additions & 0 deletions CMakeLists.txt
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)
4 changes: 3 additions & 1 deletion README.md
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).
29 changes: 29 additions & 0 deletions generator/CMakeLists.txt
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
)
243 changes: 243 additions & 0 deletions generator/src/annotated_match_callback.hpp
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 */
35 changes: 35 additions & 0 deletions generator/src/main.cpp
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());
}
10 changes: 10 additions & 0 deletions library/CMakeLists.txt
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)
Loading

0 comments on commit 12fde08

Please sign in to comment.