Implement basic functionality of moc with libtooling

This commit is contained in:
Martchus 2017-10-21 00:32:42 +02:00
parent 8344a99778
commit 888feee3f4
15 changed files with 781 additions and 95 deletions

View File

@ -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

View File

@ -0,0 +1,18 @@
// this file is based on clangversionabstraction.cpp from https://github.com/woboq/moc-ng
#include "./clangversionabstraction.h"
#include <clang/Lex/Preprocessor.h>
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

View File

@ -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 <clang/Basic/SourceLocation.h>
#include <clang/Basic/Version.h>
#include <memory>
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 <typename T> struct MaybeUnique {
T *val;
operator T *()
{
return val;
}
template <typename X> operator std::unique_ptr<X>() &&
{
return std::unique_ptr<X>(val);
}
};
template <typename T> MaybeUnique<T> maybe_unique(T *val)
{
return { val };
}
template <typename T> MaybeUnique<T> maybe_unique(std::unique_ptr<T> 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<t>
#endif
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_CLANG_VERSION_ABSTRACTION_H

81
moc/consumer.cpp Normal file
View File

@ -0,0 +1,81 @@
#include "./consumer.h"
#include <clang/AST/ASTContext.h>
#include <clang/AST/DeclCXX.h>
#include <clang/AST/DeclGroup.h>
#include <clang/AST/DeclTemplate.h>
#include <clang/Basic/MacroBuilder.h>
#include <clang/Sema/Sema.h>
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<clang::NamespaceDecl>(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<clang::DiagnosticsEngine *>(info.getDiags())->Reset();
}
}
} // namespace ReflectiveRapidJSON

85
moc/consumer.h Normal file
View File

@ -0,0 +1,85 @@
#ifndef REFLECTIVE_RAPIDJSON_CONSUMER_H
#define REFLECTIVE_RAPIDJSON_CONSUMER_H
#include "./visitor.h"
#include <clang/AST/ASTConsumer.h>
#include <clang/AST/Decl.h>
#include <clang/Basic/DiagnosticIDs.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Lex/LexDiagnostic.h>
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<DiagnosticConsumer> 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<DiagnosticConsumer> m_proxy;
unsigned int m_realErrorCount;
};
inline DiagConsumer::DiagConsumer(std::unique_ptr<clang::DiagnosticConsumer> 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

47
moc/frontendaction.cpp Normal file
View File

@ -0,0 +1,47 @@
#include "./frontendaction.h"
#include "./consumer.h"
#include <c++utilities/application/global.h>
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<clang::DiagnosticConsumer>(compilerInstance.getDiagnostics().takeClient())));
return maybe_unique(new Consumer(m_factory, compilerInstance));
}
} // namespace ReflectiveRapidJSON

34
moc/frontendaction.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef REFLECTIVE_RAPIDJSON_FRONTEND_ACTION_H
#define REFLECTIVE_RAPIDJSON_FRONTEND_ACTION_H
#include "./clangversionabstraction.h"
#include <clang/Frontend/FrontendAction.h>
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

View File

@ -1,27 +1,13 @@
#include "./generator.h"
#include "./frontendaction.h"
#include <c++utilities/application/global.h>
#include <c++utilities/conversion/stringbuilder.h>
//#include <clang/Basic/LangOptions.h>
//#include <clang/Basic/TargetInfo.h>
//#include <clang/Basic/Diagnostic.h>
//#include <clang/Frontend/TextDiagnosticPrinter.h>
//#include <clang/Lex/HeaderSearch.h>
//#include <clang/Lex/Preprocessor.h>
//#include <clang/AST/ASTContext.h>
//#include <clang/AST/ASTConsumer.h>
//#include <clang/Sema/Sema.h>
//#include <clang/Parse/ParseAST.h>
//#include <clang/AST/AST.h>
//#include <clang/AST/RecursiveASTVisitor.h>
//#include <clang/Frontend/FrontendActions.h>
//#include <clang/Frontend/CompilerInstance.h>
//#include <clang/Tooling/Tooling.h>
#include <clang-c/Index.h>
#include <clang/AST/DeclCXX.h>
#include <clang/Basic/FileManager.h>
#include <clang/Frontend/FrontendActions.h>
#include <clang/Tooling/Tooling.h>
#include <iostream>
#include <memory>
@ -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<streamsize>(str.size()));
return os;
}
struct Struct {
string ns;
string name;
vector<Struct> members;
};
bool generateReflectionCode(const vector<const char *> &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<ostream *>(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<clang::CXXRecordDecl *>(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<UTF8<char>>::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<const char *> &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<JSONSerializationCodeGenerator>());
}
CodeFactory::~CodeFactory()
{
}
/*!
* \brief Constructs arguments for the Clang tool invocation.
*/
std::vector<string> CodeFactory::makeClangArgs() const
{
static const initializer_list<const char *> flags
= { m_applicationPath, "-x", "c++", "-fPIE", "-fPIC", "-Wno-microsoft", "-Wno-pragma-once-outside-header", "-std=c++14", "-fsyntax-only" };
vector<string> 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<ToolInvocation>(*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

View File

@ -2,11 +2,97 @@
#define REFLECTIVE_RAPIDJSON_GENERATOR_H
#include <iosfwd>
#include <memory>
#include <string>
#include <vector>
namespace clang {
class Decl;
class NamedDecl;
class CXXRecordDecl;
} // namespace clang
namespace ReflectiveRapidJSON {
bool generateReflectionCode(const std::vector<const char *> &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<RelevantClass> 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<const char *> &sourceFiles, std::ostream &os);
~CodeFactory();
void addDeclaration(clang::Decl *decl);
bool readAST();
bool generate() const;
private:
struct ToolInvocation;
std::vector<std::string> makeClangArgs() const;
const char *const m_applicationPath;
const std::vector<const char *> &m_sourceFiles;
std::ostream &m_os;
std::vector<std::unique_ptr<CodeGenerator>> m_generators;
std::unique_ptr<ToolInvocation> m_toolInvocation;
};
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_GENERATOR_H

View File

@ -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;

View File

@ -2,17 +2,13 @@
#define SOME_STRUCTS_H
//#include <string>
#include "../../lib/reflectable.h"
namespace TestNamespace1 {
#define SOME_MACRO
struct Reflectable
{
};
struct Person : public Reflectable
struct Person : public ReflectiveRapidJSON::Reflectable<Person>
{
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 {

View File

@ -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<UTF8<char>>::ConstObject &value)
{
pull(reflectable.age, "age", value);
pull(reflectable.alive, "alive", value);
}
} // namespace Reflector
} // namespace ReflectiveRapidJSON

View File

@ -7,6 +7,7 @@
#include <c++utilities/io/misc.h>
#include <c++utilities/tests/testutils.h>
using TestUtilities::operator<<; // must be visible prior to the call site
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
@ -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<string> m_expectedCode;
};
CPPUNIT_TEST_SUITE_REGISTRATION(OverallTests);
/*!
* \brief Asserts equality of two iteratables printing the differing indices.
*/
template <typename Iteratable, Traits::EnableIf<Traits::IsIteratable<Iteratable>, Traits::Not<Traits::IsString<Iteratable>>>...>
inline void assertEqualityLinewise(const Iteratable &iteratable1, const Iteratable &iteratable2)
{
std::vector<std::string> 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<const char *> 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
}

68
moc/visitor.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "./visitor.h"
#include "./generator.h"
#include <c++utilities/application/global.h>
#include <clang/AST/CXXInheritance.h>
#include <iostream>
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

30
moc/visitor.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef REFLECTIVE_RAPIDJSON_VISITOR_H
#define REFLECTIVE_RAPIDJSON_VISITOR_H
#include <clang/AST/ASTContext.h>
#include <clang/AST/RecursiveASTVisitor.h>
#include <clang/Frontend/CompilerInstance.h>
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<Visitor> {
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