From 888feee3f436ff40d6af5fe71c1bea8472fe5831 Mon Sep 17 00:00:00 2001 From: Martchus Date: Sat, 21 Oct 2017 00:32:42 +0200 Subject: [PATCH] Implement basic functionality of moc with libtooling --- moc/CMakeLists.txt | 18 +- moc/clangversionabstraction.cpp | 18 ++ moc/clangversionabstraction.h | 56 +++++ moc/consumer.cpp | 81 +++++++ moc/consumer.h | 85 +++++++ moc/frontendaction.cpp | 47 ++++ moc/frontendaction.h | 34 +++ moc/generator.cpp | 227 +++++++++++++----- moc/generator.h | 88 ++++++- moc/main.cpp | 38 ++- moc/testfiles/some_structs.h | 16 +- .../some_structs_json_serialization.h | 15 ++ moc/tests/overall.cpp | 55 ++++- moc/visitor.cpp | 68 ++++++ moc/visitor.h | 30 +++ 15 files changed, 781 insertions(+), 95 deletions(-) create mode 100644 moc/clangversionabstraction.cpp create mode 100644 moc/clangversionabstraction.h create mode 100644 moc/consumer.cpp create mode 100644 moc/consumer.h create mode 100644 moc/frontendaction.cpp create mode 100644 moc/frontendaction.h create mode 100644 moc/testfiles/some_structs_json_serialization.h create mode 100644 moc/visitor.cpp create mode 100644 moc/visitor.h diff --git a/moc/CMakeLists.txt b/moc/CMakeLists.txt index 841b960..0b7697c 100644 --- a/moc/CMakeLists.txt +++ b/moc/CMakeLists.txt @@ -8,9 +8,17 @@ set(LINK_TESTS_AGAINST_APP_TARGET ON) # add project files set(HEADER_FILES generator.h + frontendaction.h + consumer.h + visitor.h + clangversionabstraction.h ) set(SRC_FILES generator.cpp + frontendaction.cpp + consumer.cpp + clangversionabstraction.cpp + visitor.cpp main.cpp ) set(TEST_HEADER_FILES @@ -26,13 +34,9 @@ use_cpp_utilities() # find libclang find_package(Clang REQUIRED) -list(APPEND PRIVATE_LIBRARIES libclang) -#list(APPEND PRIVATE_LIBRARIES clangFrontend clangLex clangAST) -if(STATIC_LINKAGE) - #list(APPEND PRIVATE_LIBRARIES clangAST) -else() - #list(APPEND PRIVATE_LIBRARIES libclang) -endif() +list(APPEND PRIVATE_LIBRARIES clangTooling) + +# also add reflective_rapidjson list(APPEND PRIVATE_LIBRARIES reflective_rapidjson) # include modules to apply configuration diff --git a/moc/clangversionabstraction.cpp b/moc/clangversionabstraction.cpp new file mode 100644 index 0000000..e106539 --- /dev/null +++ b/moc/clangversionabstraction.cpp @@ -0,0 +1,18 @@ +// this file is based on clangversionabstraction.cpp from https://github.com/woboq/moc-ng + +#include "./clangversionabstraction.h" + +#include + +namespace ReflectiveRapidJSON { + +clang::FileID createFileIDForMemBuffer(clang::Preprocessor &pp, llvm::MemoryBuffer *buffer, clang::SourceLocation location) +{ +#if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR > 4 + return pp.getSourceManager().createFileID(maybe_unique(buffer), clang::SrcMgr::C_User, 0, 0, location); +#else + return pp.getSourceManager().createFileIDForMemBuffer(buffer, clang::SrcMgr::C_User, 0, 0, location); +#endif +} + +} // namespace ReflectiveRapidJSON diff --git a/moc/clangversionabstraction.h b/moc/clangversionabstraction.h new file mode 100644 index 0000000..68fafe2 --- /dev/null +++ b/moc/clangversionabstraction.h @@ -0,0 +1,56 @@ +// this file is based on clangversionabstraction.h from https://github.com/woboq/moc-ng + +#ifndef REFLECTIVE_RAPIDJSON_CLANG_VERSION_ABSTRACTION_H +#define REFLECTIVE_RAPIDJSON_CLANG_VERSION_ABSTRACTION_H + +#include +#include + +#include + +namespace clang { +class Preprocessor; +} + +namespace ReflectiveRapidJSON { + +clang::FileID createFileIDForMemBuffer(clang::Preprocessor &pp, llvm::MemoryBuffer *buffer, clang::SourceLocation location); + +/*! + * \brief The MaybeUnique class represents either a std::unique_ptr or a raw pointer. + * \remarks This is used to support Clang < 3.6 which has not been using unique_ptr in many places yet. + */ +template struct MaybeUnique { + T *val; + operator T *() + { + return val; + } + template operator std::unique_ptr() && + { + return std::unique_ptr(val); + } +}; +template MaybeUnique maybe_unique(T *val) +{ + return { val }; +} +template MaybeUnique maybe_unique(std::unique_ptr val) +{ + return { val.release() }; +} + + /*! + * \def REFLECTIVE_RAPIDJSON_MAYBE_UNIQUE + * \brief The REFLECTIVE_RAPIDJSON_MAYBE_UNIQUE macro either expands to a std::unique_ptr or a raw pointer depending on the Clang version. + */ + +#if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 5 +#define REFLECTIVE_RAPIDJSON_MAYBE_UNIQUE(t) t * +#else +#define REFLECTIVE_RAPIDJSON_MAYBE_UNIQUE(t) std::unique_ptr +#endif + +} // namespace ReflectiveRapidJSON + +#endif // REFLECTIVE_RAPIDJSON_CLANG_VERSION_ABSTRACTION_H diff --git a/moc/consumer.cpp b/moc/consumer.cpp new file mode 100644 index 0000000..080d8a7 --- /dev/null +++ b/moc/consumer.cpp @@ -0,0 +1,81 @@ +#include "./consumer.h" + +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace ReflectiveRapidJSON { + +bool Consumer::shouldParseDecl(clang::Decl *declaration) +{ + const clang::SourceManager &sourceManager = m_compilerInstance.getSourceManager(); + return sourceManager.getFileID(sourceManager.getExpansionLoc(declaration->getSourceRange().getBegin())) == sourceManager.getMainFileID(); +} + +bool Consumer::HandleTopLevelDecl(clang::DeclGroupRef groupRefDecl) +{ + for (clang::Decl *decl : groupRefDecl) { + if (clang::NamespaceDecl *namespaceDecl = llvm::dyn_cast(decl)) { + if (!shouldParseDecl(decl)) { + continue; + } + } + } + return clang::ASTConsumer::HandleTopLevelDecl(groupRefDecl); +} + +void DiagConsumer::BeginSourceFile(const clang::LangOptions &langOpts, const clang::Preprocessor *pp) +{ + m_proxy->BeginSourceFile(langOpts, pp); +} + +void DiagConsumer::clear() +{ + m_proxy->clear(); +} + +void DiagConsumer::EndSourceFile() +{ + m_proxy->EndSourceFile(); +} + +void DiagConsumer::finish() +{ + m_proxy->finish(); +} + +/*! + * \brief Changes most errors into warnings to be able to operate also on non self-contained headers. + */ +void DiagConsumer::HandleDiagnostic(clang::DiagnosticsEngine::Level diagLevel, const clang::Diagnostic &info) +{ + const auto diagId = info.getID(); + const auto category = info.getDiags()->getDiagnosticIDs()->getCategoryNumberForDiag(diagId); + + bool shouldReset = false; + if (diagLevel >= clang::DiagnosticsEngine::Error) { + if (category == 2 || category == 4 || diagId == clang::diag::err_param_redefinition || diagId == clang::diag::err_pp_expr_bad_token_binop) { + if (!m_realErrorCount) { + shouldReset = true; + } + diagLevel = clang::DiagnosticsEngine::Warning; + } else { + ++m_realErrorCount; + } + } + + DiagnosticConsumer::HandleDiagnostic(diagLevel, info); + m_proxy->HandleDiagnostic(diagLevel, info); + + if (shouldReset) { + // FIXME: is there another way to ignore errors? + const_cast(info.getDiags())->Reset(); + } +} + +} // namespace ReflectiveRapidJSON diff --git a/moc/consumer.h b/moc/consumer.h new file mode 100644 index 0000000..9993985 --- /dev/null +++ b/moc/consumer.h @@ -0,0 +1,85 @@ +#ifndef REFLECTIVE_RAPIDJSON_CONSUMER_H +#define REFLECTIVE_RAPIDJSON_CONSUMER_H + +#include "./visitor.h" + +#include +#include +#include +#include +#include + +namespace ReflectiveRapidJSON { + +class CodeFactory; + +/*! + * \brief The Consumer class is passed to FrontendAction for handling occurrences of different elements of the file. + * + * These elements consist of top-level declarations, namespace definitions and most imporantly the whole translation unit. + * If the translations unit has occurred, that means nested elements (eg. classes) have been read completely. + * In this case, the Consumer class will trigger traversing the translation unit using a Visitor instance. + */ +class Consumer : public clang::ASTConsumer { +public: + Consumer(CodeFactory &factory, clang::CompilerInstance &compilerInstance); + + bool HandleTopLevelDecl(clang::DeclGroupRef groupRefDecl) override; + void HandleTranslationUnit(clang::ASTContext &context) override; + + bool shouldParseDecl(clang::Decl *declaration); + +private: + void handleNamespaceDefinition(clang::NamespaceDecl *nsDecl); + + CodeFactory &m_factory; + clang::CompilerInstance &m_compilerInstance; + Visitor m_visitor; +}; + +inline Consumer::Consumer(CodeFactory &factory, clang::CompilerInstance &compilerInstance) + : m_factory(factory) + , m_compilerInstance(compilerInstance) + , m_visitor(factory) +{ +} + +inline void Consumer::HandleTranslationUnit(clang::ASTContext &context) +{ + m_visitor.TraverseDecl(context.getTranslationUnitDecl()); +} + +/*! + * \brief The DiagConsumer class changes most errors into warnings. + * \remarks This class is based on MocDiagConsumer from https://github.com/woboq/moc-ng. + */ +class DiagConsumer : public clang::DiagnosticConsumer { +public: + DiagConsumer(std::unique_ptr Previous); + unsigned int realErrorCount() const; + + void BeginSourceFile(const clang::LangOptions &langOpts, const clang::Preprocessor *pp = nullptr) override; + void clear() override; + void EndSourceFile() override; + void finish() override; + void HandleDiagnostic(clang::DiagnosticsEngine::Level diagLevel, const clang::Diagnostic &info) override; + +private: + std::unique_ptr m_proxy; + unsigned int m_realErrorCount; +}; + +inline DiagConsumer::DiagConsumer(std::unique_ptr Previous) + : m_proxy(std::move(Previous)) + , m_realErrorCount(0) +{ +} + +inline unsigned int DiagConsumer::realErrorCount() const +{ + return m_realErrorCount; +} + +} // namespace ReflectiveRapidJSON + +#endif // REFLECTIVE_RAPIDJSON_CONSUMER_H diff --git a/moc/frontendaction.cpp b/moc/frontendaction.cpp new file mode 100644 index 0000000..7b55bd7 --- /dev/null +++ b/moc/frontendaction.cpp @@ -0,0 +1,47 @@ +#include "./frontendaction.h" +#include "./consumer.h" + +#include + +using namespace std; + +namespace ReflectiveRapidJSON { + +bool FrontendAction::hasCodeCompletionSupport() const +{ + return true; +} + +REFLECTIVE_RAPIDJSON_MAYBE_UNIQUE(clang::ASTConsumer) +FrontendAction::CreateASTConsumer(clang::CompilerInstance &compilerInstance, llvm::StringRef inputFile) +{ + VAR_UNUSED(inputFile) + + clang::FrontendOptions &frontendOpts = compilerInstance.getFrontendOpts(); + clang::Preprocessor &pp = compilerInstance.getPreprocessor(); + clang::LangOptions &lngOpts = compilerInstance.getLangOpts(); + + // configure frontent, preporocessor and language options + frontendOpts.SkipFunctionBodies = true; + pp.enableIncrementalProcessing(true); + pp.SetSuppressIncludeNotFoundError(true); + lngOpts.DelayedTemplateParsing = true; + + // enable all extensions + lngOpts.MicrosoftExt = true; + lngOpts.DollarIdents = true; + lngOpts.CPlusPlus11 = true; +#if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 5 + lngOpts.CPlusPlus1y = true; +#else + lngOpts.CPlusPlus14 = true; +#endif + lngOpts.GNUMode = true; + + // turn some errors into warnings + compilerInstance.getDiagnostics().setClient( + new DiagConsumer(std::unique_ptr(compilerInstance.getDiagnostics().takeClient()))); + + return maybe_unique(new Consumer(m_factory, compilerInstance)); +} +} // namespace ReflectiveRapidJSON diff --git a/moc/frontendaction.h b/moc/frontendaction.h new file mode 100644 index 0000000..1787a9a --- /dev/null +++ b/moc/frontendaction.h @@ -0,0 +1,34 @@ +#ifndef REFLECTIVE_RAPIDJSON_FRONTEND_ACTION_H +#define REFLECTIVE_RAPIDJSON_FRONTEND_ACTION_H +#include "./clangversionabstraction.h" + +#include + +namespace ReflectiveRapidJSON { + +class CodeFactory; + +/*! + * \brief The FrontendAction class instantiates the AST-Consumer (Consumer class). An instance is passed to clang::tooling::ToolInvocation. + */ +class FrontendAction : public clang::ASTFrontendAction { +public: + FrontendAction(CodeFactory &factory); + bool hasCodeCompletionSupport() const override; + +protected: + REFLECTIVE_RAPIDJSON_MAYBE_UNIQUE(clang::ASTConsumer) + CreateASTConsumer(clang::CompilerInstance &compilerInstance, llvm::StringRef inputFile) override; + +private: + CodeFactory &m_factory; +}; + +inline FrontendAction::FrontendAction(CodeFactory &factory) + : m_factory(factory) +{ +} + +} // namespace ReflectiveRapidJSON + +#endif // REFLECTIVE_RAPIDJSON_FRONTEND_ACTION_H diff --git a/moc/generator.cpp b/moc/generator.cpp index 87e0b27..23e70fa 100644 --- a/moc/generator.cpp +++ b/moc/generator.cpp @@ -1,27 +1,13 @@ #include "./generator.h" +#include "./frontendaction.h" #include #include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include - -//#include -//#include - -//#include -//#include -//#include - -#include +#include +#include +#include +#include #include #include @@ -31,64 +17,173 @@ using namespace ConversionUtilities; namespace ReflectiveRapidJSON { -ostream &operator<<(ostream &stream, const CXString &str) +/*! + * \brief Prints an LLVM string reference without instantiating a std::string first. + */ +ostream &operator<<(ostream &os, llvm::StringRef str) { - stream << clang_getCString(str); - clang_disposeString(str); - return stream; + os.write(str.data(), static_cast(str.size())); + return os; } -struct Struct { - string ns; - string name; - vector members; -}; - -bool generateReflectionCode(const vector &sourceFiles, ostream &os) +CodeGenerator::~CodeGenerator() { - bool noErrors = true; +} - for (const char *sourceFile : sourceFiles) { - CXIndex index = clang_createIndex(0, 0); - const char *const args[] = { "-x", "c++" }; - CXTranslationUnit unit = nullptr; - CXErrorCode parseRes = clang_parseTranslationUnit2(index, sourceFile, args, 2, nullptr, 0, CXTranslationUnit_None, &unit); - if (!unit && parseRes != CXError_Success) { - clang_disposeIndex(index); - throw runtime_error(argsToString("Unable to parse translation unit: ", sourceFile)); +/*! + * \brief Adds the specified \a decl to the code generator. The generator might ignore irrelevant declarations. + */ +void CodeGenerator::addDeclaration(clang::Decl *decl) +{ + VAR_UNUSED(decl) +} + +/*! + * \brief Returns whether the specified \a record inherits from an instantiation of the specified \a templateClass. + * \remarks The specified \a record must be defined (not only forward-declared). + */ +bool CodeGenerator::inheritsFromInstantiationOf(clang::CXXRecordDecl *const record, const char *const templateClass) +{ + for (const clang::CXXBaseSpecifier &base : record->bases()) { + const clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl(); + if (baseDecl && baseDecl->getQualifiedNameAsString() == templateClass) { + return true; } + } + return false; +} - CXCursor cursor = clang_getTranslationUnitCursor(unit); - clang_visitChildren(cursor, - [](CXCursor c, CXCursor parent, CXClientData client_data) { - VAR_UNUSED(parent) - auto &os = *reinterpret_cast(client_data); - os << "Cursor kind '" << clang_getCursorKindSpelling(clang_getCursorKind(c)) << "\' "; - os << clang_getCursorSpelling(c) << '\n'; - os << "type: " << clang_getTypeSpelling(clang_getCursorType(c)) << '\n'; - return CXChildVisit_Recurse; - }, - &os); - os.flush(); - - for (unsigned int index = 0, diagnosticCount = clang_getNumDiagnostics(unit); index != diagnosticCount; ++index) { - noErrors = false; - CXDiagnostic diagnostic = clang_getDiagnostic(unit, index); - clang_getDiagnosticCategory(diagnostic); - - CXString diagnosticSpelling = clang_formatDiagnostic( - diagnostic, CXDiagnostic_DisplaySourceLocation | CXDiagnostic_DisplayColumn | CXDiagnostic_DisplaySourceRanges); - cerr << clang_getCString(diagnosticSpelling) << '\n'; - clang_disposeString(diagnosticSpelling); - clang_disposeDiagnostic(diagnostic); +void JSONSerializationCodeGenerator::addDeclaration(clang::Decl *decl) +{ + switch (decl->getKind()) { + case clang::Decl::Kind::CXXRecord: { + auto *const record = static_cast(decl); + // skip forward declarations + if (!record->hasDefinition()) { + return; } - cerr.flush(); + // add classes derived from any instantiation of "ReflectiveRapidJSON::Reflectable" + if (inheritsFromInstantiationOf(record, "ReflectiveRapidJSON::Reflectable")) { + m_relevantClasses.emplace_back(record->getQualifiedNameAsString(), record); + } + break; + } + case clang::Decl::Kind::Enum: + // TODO: add enums + break; + default:; + } +} - clang_disposeTranslationUnit(unit); - clang_disposeIndex(index); +void JSONSerializationCodeGenerator::generate(ostream &os) const +{ + if (m_relevantClasses.empty()) { + return; } - return noErrors; + // put everything into ReflectiveRapidJSON::Reflector + os << "namespace ReflectiveRapidJSON {\n" + "namespace Reflector {\n"; + + // add push and pull functions for each class, for an example of the resulting + // output, see ../lib/tests/reflector.cpp (code under comment "pretend serialization code...") + for (const RelevantClass &relevantClass : m_relevantClasses) { + // print push method + os << "template <> inline void push<::" << relevantClass.qualifiedName << ">(const " << relevantClass.qualifiedName + << " &reflectable, Value::Object &value, Document::AllocatorType &allocator)\n{\n"; + for (const clang::FieldDecl *field : relevantClass.record->fields()) { + os << " push(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, allocator);\n"; + } + os << "}\n"; + + // print pull method + os << "template <> inline void pull<::" << relevantClass.qualifiedName << ">(" << relevantClass.qualifiedName + << " &reflectable, const GenericValue>::ConstObject &value)\n{\n"; + for (const clang::FieldDecl *field : relevantClass.record->fields()) { + os << " pull(reflectable." << field->getName() << ", \"" << field->getName() << "\", value);\n"; + } + os << "}\n\n"; + } + + // close namespace ReflectiveRapidJSON::Reflector + os << "} // namespace Reflector\n" + "} // namespace ReflectiveRapidJSON\n"; +} + +struct CodeFactory::ToolInvocation { + ToolInvocation(CodeFactory &factory); + + clang::FileManager fileManager; + clang::tooling::ToolInvocation invocation; +}; + +CodeFactory::ToolInvocation::ToolInvocation(CodeFactory &factory) + : fileManager({ "." }) + , invocation(factory.makeClangArgs(), new FrontendAction(factory), &fileManager) +{ + fileManager.Retain(); +} + +CodeFactory::CodeFactory(const char *applicationPath, const std::vector &sourceFiles, std::ostream &os) + : m_applicationPath(applicationPath) + , m_sourceFiles(sourceFiles) + , m_os(os) +{ + // for now, add the code generator for JSON (de)serialization by default + m_generators.emplace_back(std::make_unique()); +} + +CodeFactory::~CodeFactory() +{ +} + +/*! + * \brief Constructs arguments for the Clang tool invocation. + */ +std::vector CodeFactory::makeClangArgs() const +{ + static const initializer_list flags + = { m_applicationPath, "-x", "c++", "-fPIE", "-fPIC", "-Wno-microsoft", "-Wno-pragma-once-outside-header", "-std=c++14", "-fsyntax-only" }; + vector clangArgs; + clangArgs.reserve(flags.size() + m_sourceFiles.size()); + clangArgs.insert(clangArgs.end(), flags.begin(), flags.end()); + clangArgs.insert(clangArgs.end(), m_sourceFiles.cbegin(), m_sourceFiles.cend()); + return clangArgs; +} + +/*! + * \brief Adds the specified \a decl to all underlying code generators. The generators might ignore irrelevant declarations. + * \remarks Supposed to be called by assigned generators inside readAST(). + */ +void CodeFactory::addDeclaration(clang::Decl *decl) +{ + for (const auto &generator : m_generators) { + generator->addDeclaration(decl); + } +} + +/*! + * \brief Reads (relevent) AST elements using Clang. + */ +bool CodeFactory::readAST() +{ + // lazy initialize Clang tool invocation + if (!m_toolInvocation) { + m_toolInvocation = make_unique(*this); + } + // run Clang + return m_toolInvocation->invocation.run(); +} + +/*! + * \brief Generates code based on the AST elements which have been read by invoking readAST(). + */ +bool CodeFactory::generate() const +{ + for (const auto &generator : m_generators) { + generator->generate(m_os); + } + return true; } } // namespace ReflectiveRapidJSON diff --git a/moc/generator.h b/moc/generator.h index 2502562..32eaa3d 100644 --- a/moc/generator.h +++ b/moc/generator.h @@ -2,11 +2,97 @@ #define REFLECTIVE_RAPIDJSON_GENERATOR_H #include +#include +#include #include +namespace clang { +class Decl; +class NamedDecl; +class CXXRecordDecl; +} // namespace clang + namespace ReflectiveRapidJSON { -bool generateReflectionCode(const std::vector &sourceFiles, std::ostream &os); +/*! + * \brief The CodeGenerator class is the base for generators used by the CodeFactory class. + */ +class CodeGenerator { +public: + CodeGenerator(); + virtual ~CodeGenerator(); + + virtual void addDeclaration(clang::Decl *decl); + + /// \brief Generates code based on the previously added declarations. The code is written to \a os. + virtual void generate(std::ostream &os) const = 0; + +protected: + static bool inheritsFromInstantiationOf(clang::CXXRecordDecl *record, const char *templateClass); +}; + +inline CodeGenerator::CodeGenerator() +{ } +/*! + * \brief The JSONSerializationCodeGenerator class generates code for JSON (de)serialization. + */ +class JSONSerializationCodeGenerator : public CodeGenerator { +public: + JSONSerializationCodeGenerator(); + + void addDeclaration(clang::Decl *decl) override; + void generate(std::ostream &os) const override; + +private: + struct RelevantClass { + explicit RelevantClass(const std::string &qualifiedName, clang::CXXRecordDecl *record); + + std::string qualifiedName; + clang::CXXRecordDecl *record; + }; + + std::vector m_relevantClasses; +}; + +inline JSONSerializationCodeGenerator::JSONSerializationCodeGenerator() +{ +} + +inline JSONSerializationCodeGenerator::RelevantClass::RelevantClass(const std::string &qualifiedName, clang::CXXRecordDecl *record) + : qualifiedName(qualifiedName) + , record(record) +{ +} + +/*! + * \brief The CodeFactory class produces additional (reflection) code for a specified list of C++ source files. + * \remarks + * - The code is written to a specified std::ostream instance. + * - The CodeFactory class is constituted by its underlying CodeGenerator instances. + */ +class CodeFactory { +public: + CodeFactory(const char *applicationPath, const std::vector &sourceFiles, std::ostream &os); + ~CodeFactory(); + + void addDeclaration(clang::Decl *decl); + bool readAST(); + bool generate() const; + +private: + struct ToolInvocation; + + std::vector makeClangArgs() const; + + const char *const m_applicationPath; + const std::vector &m_sourceFiles; + std::ostream &m_os; + std::vector> m_generators; + std::unique_ptr m_toolInvocation; +}; + +} // namespace ReflectiveRapidJSON + #endif // REFLECTIVE_RAPIDJSON_GENERATOR_H diff --git a/moc/main.cpp b/moc/main.cpp index bda7bc2..9bc8d95 100644 --- a/moc/main.cpp +++ b/moc/main.cpp @@ -38,14 +38,17 @@ int main(int argc, char *argv[]) // parse arguments parser.parseArgsOrExit(argc, argv); - if (!helpArg.isPresent() && !inputFilesArg.isPresent()) { + if (helpArg.isPresent()) { + return 0; + } + if (!inputFilesArg.isPresent()) { cerr << Phrases::Error << "No input file specified." << Phrases::EndFlush; - return -2; + return -1; } // setup output stream + ostream *os = nullptr; try { - ostream *os; ofstream outputFile; if (outputFileArg.isPresent()) { outputFile.exceptions(ios_base::badbit | ios_base::failbit); @@ -55,12 +58,33 @@ int main(int argc, char *argv[]) os = &cout; } - // process input files - return generateReflectionCode(inputFilesArg.values(0), *os) ? 0 : 1; + // configure code generator + CodeFactory factory(parser.executable(), inputFilesArg.values(0), *os); + // TODO: make code generator configurable, eg. + // factory.addGenerator(...); + + // read AST elements from input files + if (!factory.readAST()) { + cerr << Phrases::Error << "Errors occured when parsing the input files." << Phrases::EndFlush; + return -2; + } + + // run the code generator + if (!factory.generate()) { + cerr << Phrases::Error << "Errors occured when during code generation." << Phrases::EndFlush; + return -3; + } + } catch (...) { catchIoFailure(); - cerr << Phrases::Error << "An IO error occured." << Phrases::EndFlush; - return -3; + const char *errorMessage; + if (os) { + errorMessage = os->fail() || os->bad() ? "An IO error occured when writing to the output stream." : "An IO error occured."; + } else { + errorMessage = "An IO error when opening output stream."; + } + cerr << Phrases::Error << errorMessage << Phrases::EndFlush; + return -4; } return 0; diff --git a/moc/testfiles/some_structs.h b/moc/testfiles/some_structs.h index 8d182e3..f063f5c 100644 --- a/moc/testfiles/some_structs.h +++ b/moc/testfiles/some_structs.h @@ -2,17 +2,13 @@ #define SOME_STRUCTS_H //#include +#include "../../lib/reflectable.h" namespace TestNamespace1 { #define SOME_MACRO -struct Reflectable -{ - -}; - -struct Person : public Reflectable +struct Person : public ReflectiveRapidJSON::Reflectable { SOME_MACRO //std::string name; @@ -20,6 +16,14 @@ struct Person : public Reflectable bool alive; }; +struct NonReflectableClass +{ + int foo; +} + +struct SomeOtherNonReflectableClass : public NonReflectableClass +{ + int bar; } namespace TestNamespace2 { diff --git a/moc/testfiles/some_structs_json_serialization.h b/moc/testfiles/some_structs_json_serialization.h new file mode 100644 index 0000000..f387379 --- /dev/null +++ b/moc/testfiles/some_structs_json_serialization.h @@ -0,0 +1,15 @@ +namespace ReflectiveRapidJSON { +namespace Reflector { +template <> inline void push<::TestNamespace1::Person>(const TestNamespace1::Person &reflectable, Value::Object &value, Document::AllocatorType &allocator) +{ + push(reflectable.age, "age", value, allocator); + push(reflectable.alive, "alive", value, allocator); +} +template <> inline void pull<::TestNamespace1::Person>(TestNamespace1::Person &reflectable, const GenericValue>::ConstObject &value) +{ + pull(reflectable.age, "age", value); + pull(reflectable.alive, "alive", value); +} + +} // namespace Reflector +} // namespace ReflectiveRapidJSON diff --git a/moc/tests/overall.cpp b/moc/tests/overall.cpp index 3d7c11d..39ddb76 100644 --- a/moc/tests/overall.cpp +++ b/moc/tests/overall.cpp @@ -7,6 +7,7 @@ #include #include +using TestUtilities::operator<<; // must be visible prior to the call site #include #include @@ -17,13 +18,16 @@ using namespace std; using namespace CPPUNIT_NS; using namespace IoUtilities; -using namespace ConversionUtilities; using namespace TestUtilities; +using namespace ConversionUtilities; using namespace ReflectiveRapidJSON; +/*! + * \brief The OverallTests class tests the overall functionality of the code generator (CLI and generator itself). + */ class OverallTests : public TestFixture { CPPUNIT_TEST_SUITE(OverallTests); - CPPUNIT_TEST(testGenerator); + CPPUNIT_TEST(testGeneratorItself); CPPUNIT_TEST(testCLI); CPPUNIT_TEST_SUITE_END(); @@ -31,37 +35,72 @@ public: void setUp(); void tearDown(); - void testGenerator(); + void testGeneratorItself(); void testCLI(); private: + vector m_expectedCode; }; CPPUNIT_TEST_SUITE_REGISTRATION(OverallTests); +/*! + * \brief Asserts equality of two iteratables printing the differing indices. + */ +template , Traits::Not>>...> +inline void assertEqualityLinewise(const Iteratable &iteratable1, const Iteratable &iteratable2) +{ + std::vector differentLines; + std::size_t currentLine = 0; + + for (auto i1 = iteratable1.cbegin(), i2 = iteratable2.cbegin(); i1 != iteratable1.cend() || i2 != iteratable2.cend(); ++currentLine) { + if (i1 != iteratable1.cend() && i2 != iteratable2.cend()) { + if (*i1 != *i2) { + differentLines.push_back(numberToString(currentLine)); + } + ++i1, ++i2; + } else if (i1 != iteratable1.cend()) { + differentLines.push_back(numberToString(currentLine)); + ++i1; + } else if (i2 != iteratable1.cend()) { + differentLines.push_back(numberToString(currentLine)); + ++i2; + } + } + if (!differentLines.empty()) { + CPPUNIT_ASSERT_EQUAL_MESSAGE("the following lines differ: " + joinStrings(differentLines, ", "), iteratable1, iteratable2); + } +} + void OverallTests::setUp() { + m_expectedCode = toArrayOfLines(readFile(testFilePath("some_structs_json_serialization.h"), 1024)); } void OverallTests::tearDown() { } -void OverallTests::testGenerator() +void OverallTests::testGeneratorItself() { const string inputFilePath(testFilePath("some_structs.h")); + const vector inputFiles{ inputFilePath.data() }; + stringstream buffer; - generateReflectionCode({ inputFilePath.data() }, buffer); - CPPUNIT_ASSERT_EQUAL("test"s, buffer.str()); + CodeFactory factory(TestApplication::appPath(), inputFiles, buffer); + CPPUNIT_ASSERT(factory.readAST()); + CPPUNIT_ASSERT(factory.generate()); + assertEqualityLinewise(m_expectedCode, toArrayOfLines(buffer.str())); } void OverallTests::testCLI() { #ifdef PLATFORM_UNIX - const string inputFilePath(testFilePath("some_structs.h")); string stdout, stderr; + + const string inputFilePath(testFilePath("some_structs.h")); const char *const args1[] = { PROJECT_NAME, "-i", inputFilePath.data(), nullptr }; TESTUTILS_ASSERT_EXEC(args1); - CPPUNIT_ASSERT_EQUAL("test"s, stdout); + assertEqualityLinewise(m_expectedCode, toArrayOfLines(stdout)); #endif } diff --git a/moc/visitor.cpp b/moc/visitor.cpp new file mode 100644 index 0000000..e766402 --- /dev/null +++ b/moc/visitor.cpp @@ -0,0 +1,68 @@ +#include "./visitor.h" +#include "./generator.h" + +#include + +#include + +#include + +using namespace std; + +namespace ReflectiveRapidJSON { + +/*! + * \brief Constructs a new Visitor. + */ +Visitor::Visitor(CodeFactory &factory) + : m_factory(factory) +{ +} + +/*! + * \brief Adds any kind of declaration to the factory. + */ +bool Visitor::VisitDecl(clang::Decl *decl) +{ + m_factory.addDeclaration(decl); + return true; +} + +/*! + * \brief Visits function declarations. Currently not used. + * \remarks Might be used later to detect functions. + */ +bool ReflectiveRapidJSON::Visitor::VisitFunctionDecl(clang::FunctionDecl *func) +{ + VAR_UNUSED(func) + return true; +} + +/*! + * \brief Visits statements. Currently not used. + */ +bool ReflectiveRapidJSON::Visitor::VisitStmt(clang::Stmt *st) +{ + VAR_UNUSED(st) + return true; +} + +/*! + * \brief Visits namespace declarations. Currently not used. + */ +bool Visitor::VisitNamespaceDecl(clang::NamespaceDecl *decl) +{ + VAR_UNUSED(decl) + return true; +} + +/*! + * \brief Visits classes and class templates. Currently not used. + */ +bool Visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *decl) +{ + VAR_UNUSED(decl) + return true; +} + +} // namespace ReflectiveRapidJSON diff --git a/moc/visitor.h b/moc/visitor.h new file mode 100644 index 0000000..f529330 --- /dev/null +++ b/moc/visitor.h @@ -0,0 +1,30 @@ +#ifndef REFLECTIVE_RAPIDJSON_VISITOR_H +#define REFLECTIVE_RAPIDJSON_VISITOR_H + +#include +#include +#include + +namespace ReflectiveRapidJSON { + +class CodeFactory; + +/*! + * \brief The Visitor class is used to traverse the elements of a translation unit. For this purpose, it is instantiated by the Consumer class. + */ +class Visitor : public clang::RecursiveASTVisitor { +public: + explicit Visitor(CodeFactory &factory); + bool VisitDecl(clang::Decl *decl); + bool VisitFunctionDecl(clang::FunctionDecl *func); + bool VisitStmt(clang::Stmt *st); + bool VisitNamespaceDecl(clang::NamespaceDecl *decl); + bool VisitCXXRecordDecl(clang::CXXRecordDecl *decl); + +private: + CodeFactory &m_factory; +}; + +} // namespace ReflectiveRapidJSON + +#endif // REFLECTIVE_RAPIDJSON_VISITOR_H