Add examples from Meeting C++ talk

This commit is contained in:
Jonathan Müller 2017-11-11 10:45:37 +01:00
commit f73e4b420f
20 changed files with 964 additions and 85 deletions

View file

@ -15,6 +15,7 @@ option(CPPAST_ENABLE_ASSERTIONS "whether or not to enable internal assertions fo
option(CPPAST_ENABLE_PRECONDITION_CHECKS "whether or not to enable precondition checks" ON)
option(CPPAST_BUILD_TEST "whether or not to build the tests" ON)
option(CPPAST_BUILD_EXAMPLE "whether or not to build the examples" ON)
option(CPPAST_BUILD_TOOL "whether or not to build the tool" ON)
option(BUILD_TESTING "build test" OFF) # The ctest variable for building tests
@ -24,6 +25,12 @@ else()
set(build_test OFF)
endif()
if(${CPPAST_BUILD_EXAMPLE} OR (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR))
set(build_example ON)
else()
set(build_example OFF)
endif()
if(${CPPAST_BUILD_TOOL} OR (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR))
set(build_tool ON)
else()
@ -43,6 +50,9 @@ add_subdirectory(src)
if(${build_test})
add_subdirectory(test)
endif()
if(${build_example})
add_subdirectory(example)
endif()
if(${build_tool})
add_subdirectory(tool)
endif()

16
example/CMakeLists.txt Normal file
View file

@ -0,0 +1,16 @@
# Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
# This file is subject to the license terms in the LICENSE file
# found in the top-level directory of this distribution.
function(_cppast_example name)
add_executable(cppast_example_${name} ${name}.cpp example_parser.hpp)
target_link_libraries(cppast_example_${name} PUBLIC cppast)
set_target_properties(cppast_example_${name} PROPERTIES CXX_STANDARD 11)
endfunction()
_cppast_example(ast_printer)
_cppast_example(comparison)
_cppast_example(documentation_generator)
_cppast_example(enum_category)
_cppast_example(enum_to_string)
_cppast_example(serialization)

33
example/ast_printer.cpp Normal file
View file

@ -0,0 +1,33 @@
// Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
/// \file
/// This is a very primitive version of the cppast tool.
#include <cppast/visitor.hpp> // visit()
#include "example_parser.hpp"
void print_ast(const cppast::cpp_file& file)
{
std::string prefix;
// visit each entity in the file
cppast::visit(file, [&](const cppast::cpp_entity& e, cppast::visitor_info info) {
if (info.event == cppast::visitor_info::container_entity_exit) // exiting an old container
prefix.pop_back();
else if (info.event == cppast::visitor_info::container_entity_enter)
// entering a new container
{
std::cout << prefix << "'" << e.name() << "' - " << cppast::to_string(e.kind()) << '\n';
prefix += "\t";
}
else // if (info.event == cppast::visitor_info::leaf_entity) // a non-container entity
std::cout << prefix << "'" << e.name() << "' - " << cppast::to_string(e.kind()) << '\n';
});
}
int main(int argc, char* argv[])
{
return example_main(argc, argv, {}, &print_ast);
}

117
example/comparison.cpp Normal file
View file

@ -0,0 +1,117 @@
// Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
/// \file
/// Generate equality comparisons.
#include <algorithm>
#include <iostream>
#include <cppast/cpp_class.hpp>
#include <cppast/cpp_member_variable.hpp>
#include <cppast/visitor.hpp>
#include "example_parser.hpp"
bool has_token(const cppast::cpp_token_string& str, const char* token)
{
auto iter = std::find_if(str.begin(), str.end(),
[&](const cppast::cpp_token& tok) { return tok.spelling == token; });
return iter != str.end();
}
void generate_op_equal(std::ostream& out, const cppast::cpp_class& c)
{
out << "inline bool operator==(const " << c.name() << "& lhs, const " << c.name()
<< "& rhs) {\n";
out << " return ";
auto first = true;
for (auto& base : c.bases())
{
if (cppast::has_attribute(base, "generate::transient"))
continue;
if (first)
first = false;
else
out << " && ";
out << "static_cast<const " << base.name() << "&>(lhs) == static_cast<const " << base.name()
<< "&>(rhs)\n";
}
for (auto& member : c)
if (member.kind() == cppast::cpp_entity_kind::member_variable_t
&& !cppast::has_attribute(member, "generate::transient"))
{
// generate comparison code for non-transient member variables
if (first)
first = false;
else
out << " && ";
out << "lhs." << member.name() << " == "
<< "rhs." << member.name() << "\n";
}
out << " ;\n";
out << "}\n\n";
}
void generate_op_non_equal(std::ostream& out, const cppast::cpp_class& c)
{
out << "inline bool operator!=(const " << c.name() << "& lhs, const " << c.name()
<< "& rhs) {\n";
out << " return !(lhs == rhs);\n";
out << "}\n\n";
}
void generate_comparison(const cppast::cpp_file& file)
{
cppast::visit(file,
[](const cppast::cpp_entity& e) {
// only visit non-templated class definitions that have the attribute set
return (!cppast::is_templated(e)
&& e.kind() == cppast::cpp_entity_kind::class_t
&& cppast::is_definition(e)
&& cppast::has_attribute(e, "generate::comparison"))
// or all namespaces
|| e.kind() == cppast::cpp_entity_kind::namespace_t;
},
[](const cppast::cpp_entity& e, const cppast::visitor_info& info) {
if (e.kind() == cppast::cpp_entity_kind::class_t && !info.is_old_entity())
{
auto& class_ = static_cast<const cppast::cpp_class&>(e);
auto& attribute =
cppast::has_attribute(e, "generate::comparison").value();
if (attribute.arguments())
{
if (has_token(attribute.arguments().value(), "=="))
generate_op_equal(std::cout, class_);
if (has_token(attribute.arguments().value(), "!="))
generate_op_non_equal(std::cout, class_);
}
else
{
generate_op_equal(std::cout, class_);
generate_op_non_equal(std::cout, class_);
}
}
else if (e.kind() == cppast::cpp_entity_kind::namespace_t)
{
if (info.event == cppast::visitor_info::container_entity_enter)
// open namespace
std::cout << "namespace " << e.name() << " {\n\n";
else // if (info.event == cppast::visitor_info::container_entity_exit)
// close namespace
std::cout << "}\n";
}
});
}
int main(int argc, char* argv[])
{
return example_main(argc, argv, {}, &generate_comparison);
}

View file

@ -0,0 +1,160 @@
// Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
/// \file
/// A primitive documentation generator.
#include <cppast/code_generator.hpp> // code_generator, generate_code()
#include <cppast/visitor.hpp> // visit()
#include "example_parser.hpp"
// don't show in code generation
bool is_excluded_synopsis(const cppast::cpp_entity& e, cppast::cpp_access_specifier_kind access)
{
// exclude privates and those marked for exclusion
return access == cppast::cpp_private || cppast::has_attribute(e, "documentation::exclude");
}
// don't show in documentation
bool is_excluded_documentation(const cppast::cpp_entity& e,
const cppast::cpp_access_specifier_kind access)
{
// exclude uninteresting entities
return e.kind() == cppast::cpp_entity_kind::access_specifier_t
|| e.kind() == cppast::cpp_entity_kind::using_declaration_t
|| e.kind() == cppast::cpp_entity_kind::using_directive_t
|| e.kind() == cppast::cpp_entity_kind::static_assert_t
|| e.kind() == cppast::cpp_entity_kind::include_directive_t
// and all excluded in synopsis
|| is_excluded_synopsis(e, access);
}
// generates synopsis of an entity
std::string generate_synopsis(const cppast::cpp_entity& e)
{
// the generator for the synopsis
class synopsis_generator final : public cppast::code_generator
{
public:
// get the resulting string
std::string result()
{
return std::move(str_);
}
private:
// whether or not the entity is the main entity that is being documented
bool is_main_entity(const cppast::cpp_entity& e)
{
if (cppast::is_templated(e) || cppast::is_friended(e))
// need to ask the real entity
return is_main_entity(e.parent().value());
else
return &e == &this->main_entity();
}
// get some nicer formatting
cppast::formatting do_get_formatting() const override
{
return cppast::formatting_flags::brace_nl | cppast::formatting_flags::comma_ws
| cppast::formatting_flags::operator_ws;
}
// calculate generation options
generation_options do_get_options(const cppast::cpp_entity& e,
cppast::cpp_access_specifier_kind access) override
{
if (is_excluded_synopsis(e, access))
return cppast::code_generator::exclude;
else if (!is_main_entity(e))
// only generation declaration for the non-documented entity
return cppast::code_generator::declaration;
else
// default options
return {};
}
// update indendation level
void do_indent() override
{
++indent_;
}
void do_unindent() override
{
if (indent_)
--indent_;
}
// write specified tokens
// need to change indentation for each newline
void do_write_token_seq(cppast::string_view tokens) override
{
if (was_newline_)
{
str_ += std::string(indent_ * 2u, ' ');
was_newline_ = false;
}
str_ += tokens.c_str();
}
// write + remember newline
void do_write_newline() override
{
str_ += "\n";
was_newline_ = true;
}
std::string str_;
unsigned indent_ = 0;
bool was_newline_ = false;
} generator;
cppast::generate_code(generator, e);
return generator.result();
}
void generate_documentation(const cppast::cpp_file& file)
{
// visit each entity
cppast::visit(file,
[](const cppast::cpp_entity& e, cppast::cpp_access_specifier_kind access) {
if (is_excluded_documentation(e, access))
// exclude this and all children
return cppast::visit_filter::exclude_and_children;
else if (cppast::is_templated(e) || cppast::is_friended(e))
// continue on with children for a dummy entity
return cppast::visit_filter::exclude;
else
return cppast::visit_filter::include;
},
[](const cppast::cpp_entity& e, const cppast::visitor_info& info) {
if (info.is_old_entity())
// already done
return;
// print name
std::cout << "## " << cppast::to_string(e.kind()) << " '" << e.name()
<< "'\n";
std::cout << '\n';
// print synopsis
std::cout << "```\n";
std::cout << generate_synopsis(e);
std::cout << "```\n\n";
// print documentation comment
if (e.comment())
std::cout << e.comment().value() << '\n';
// print separator
std::cout << "\n---\n\n";
});
std::cout << "\n\n";
}
int main(int argc, char* argv[])
{
return example_main(argc, argv, {}, &generate_documentation);
}

116
example/enum_category.cpp Normal file
View file

@ -0,0 +1,116 @@
// Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
/// \file
/// Generates enum category functions.
#include <algorithm>
#include <cassert>
#include <iostream>
#include <cppast/cpp_enum.hpp> // cpp_enum
#include <cppast/cpp_function.hpp> // cpp_function
#include <cppast/visitor.hpp> // visit()
#include "example_parser.hpp"
bool is_category(const cppast::cpp_enum_value& e, const std::string& name)
{
if (auto attr = cppast::has_attribute(e, "generate::enum_category"))
{
auto iter =
std::find_if(attr.value().arguments().value().begin(),
attr.value().arguments().value().end(),
[&](const cppast::cpp_token& tok) { return tok.spelling == name; });
return iter != attr.value().arguments().value().end();
}
else
return false;
}
const cppast::cpp_enum& get_enum(const cppast::cpp_entity_index& index,
const cppast::cpp_function_parameter& param)
{
auto& param_type = param.type();
// is an enum
assert(param_type.kind() == cppast::cpp_type_kind::user_defined_t);
auto& definition =
static_cast<const cppast::cpp_user_defined_type&>(param_type).entity().get(index)[0u].get();
assert(definition.kind() == cppast::cpp_entity_kind::enum_t);
return static_cast<const cppast::cpp_enum&>(definition);
}
void generate_enum_category(const cppast::cpp_entity_index& index, const cppast::cpp_file& file)
{
cppast::visit(file,
[](const cppast::cpp_entity& e) {
// only visit function declarations that have the attribute set
return (e.kind() == cppast::cpp_entity_kind::function_t
&& !cppast::is_definition(e)
&& cppast::has_attribute(e, "generate::enum_category"))
// or all namespaces
|| e.kind() == cppast::cpp_entity_kind::namespace_t;
},
[&](const cppast::cpp_entity& e, const cppast::visitor_info& info) {
if (e.kind() == cppast::cpp_entity_kind::function_t)
{
// a new function, generate implementation
assert(info.is_new_entity());
auto category = cppast::has_attribute(e, "generate::enum_category")
.value()
.arguments()
.value()
.as_string();
auto& func = static_cast<const cppast::cpp_function&>(e);
// return type must be bool
assert(func.return_type().kind() == cppast::cpp_type_kind::builtin_t
&& static_cast<const cppast::cpp_builtin_type&>(func.return_type())
.builtin_type_kind()
== cppast::cpp_bool);
// single parameter...
assert(std::next(func.parameters().begin()) == func.parameters().end());
auto& param = *func.parameters().begin();
auto& enum_ = get_enum(index, param);
// generate function definition
std::cout << "inline bool " << func.name() << "("
<< cppast::to_string(param.type()) << " e) {\n";
// generate switch
std::cout << " switch (e) {\n";
for (const auto& enumerator : enum_)
{
std::cout << " case " << enum_.name() << "::" << enumerator.name()
<< ":\n";
if (is_category(enumerator, category))
std::cout << " return true;\n";
else
std::cout << " return false;\n";
}
std::cout << " }\n";
std::cout << "}\n\n";
}
else if (e.kind() == cppast::cpp_entity_kind::namespace_t)
{
if (info.event == cppast::visitor_info::container_entity_enter)
// open namespace
std::cout << "namespace " << e.name() << " {\n\n";
else // if (info.event == cppast::visitor_info::container_entity_exit)
// close namespace
std::cout << "}\n";
}
});
}
int main(int argc, char* argv[])
{
cppast::cpp_entity_index index;
return example_main(argc, argv, index,
[&](const cppast::cpp_file& file) { generate_enum_category(index, file); });
}

View file

@ -0,0 +1,70 @@
// Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
/// \file
/// Generates enum `to_string()` code.
#include <iostream>
#include <cppast/cpp_enum.hpp> // cpp_enum
#include <cppast/visitor.hpp> // visit()
#include "example_parser.hpp"
void generate_to_string(const cppast::cpp_file& file)
{
cppast::visit(file,
[](const cppast::cpp_entity& e) {
// only visit enum definitions that have the attribute set
return (e.kind() == cppast::cpp_entity_kind::enum_t
&& cppast::is_definition(e)
&& cppast::has_attribute(e, "generate::to_string"))
// or all namespaces
|| e.kind() == cppast::cpp_entity_kind::namespace_t;
},
[](const cppast::cpp_entity& e, const cppast::visitor_info& info) {
if (e.kind() == cppast::cpp_entity_kind::enum_t && !info.is_old_entity())
{
// a new enum, generate to string function
auto& enum_ = static_cast<const cppast::cpp_enum&>(e);
// write function header
std::cout << "inline const char* to_string(const " << enum_.name()
<< "& e) {\n";
// generate switch
std::cout << " switch (e) {\n";
for (const auto& enumerator : enum_)
{
std::cout << " case " << enum_.name() << "::" << enumerator.name()
<< ":\n";
if (auto attr =
cppast::has_attribute(enumerator, "generate::to_string"))
std::cout << " return "
<< attr.value().arguments().value().as_string()
<< ";\n";
else
std::cout << " return \"" << enumerator.name() << "\";\n";
}
std::cout << " }\n";
std::cout << "}\n\n";
}
else if (e.kind() == cppast::cpp_entity_kind::namespace_t)
{
if (info.event == cppast::visitor_info::container_entity_enter)
// open namespace
std::cout << "namespace " << e.name() << " {\n\n";
else // if (info.event == cppast::visitor_info::container_entity_exit)
// close namespace
std::cout << "}\n";
}
});
}
int main(int argc, char* argv[])
{
return example_main(argc, argv, {}, &generate_to_string);
}

View file

@ -0,0 +1,56 @@
// Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef CPPAST_EXAMPLE_PARSER_HPP_INCLUDED
#define CPPAST_EXAMPLE_PARSER_HPP_INCLUDED
#include <iostream>
#include <cppast/libclang_parser.hpp>
// reads the database directory from the command line argument
// parses all files in that directory
// and invokes the callback for each of them
template <typename Callback>
int example_main(int argc, char* argv[], const cppast::cpp_entity_index& index, Callback cb) try
{
if (argc != 2)
{
std::cerr << "usage: " << argv[0] << " <compile-commands-json-dir>\n";
return 1;
}
else
{
cppast::libclang_compilation_database database(argv[1]); // the compilation database
// simple_file_parser allows parsing multiple files and stores the results for us
cppast::simple_file_parser<cppast::libclang_parser> parser(type_safe::ref(index));
try
{
cppast::parse_database(parser, database); // parse all files in the database
}
catch (cppast::libclang_error& ex)
{
std::cerr << "fatal libclang error: " << ex.what() << '\n';
return 1;
}
if (parser.error())
// a non-fatal parse error
// error has been logged to stderr
return 1;
for (auto& file : parser.files())
cb(file);
}
return 0;
}
catch (std::exception& ex)
{
std::cerr << ex.what() << '\n';
return 1;
}
#endif // CPPAST_EXAMPLE_PARSER_HPP_INCLUDED

116
example/serialization.cpp Normal file
View file

@ -0,0 +1,116 @@
// Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
/// \file
/// Serialization code generation.
#include <iostream>
#include <cppast/cpp_class.hpp>
#include <cppast/cpp_member_variable.hpp>
#include <cppast/cpp_type.hpp>
#include <cppast/visitor.hpp>
#include "example_parser.hpp"
// whether or not a type is a C string, i.e. char pointer
bool is_c_string(const cppast::cpp_type& type)
{
if (type.kind() != cppast::cpp_type_kind::pointer_t)
return false;
auto& pointee = cppast::remove_cv(static_cast<const cppast::cpp_pointer_type&>(type).pointee());
if (pointee.kind() != cppast::cpp_type_kind::builtin_t)
return false;
auto builtin = static_cast<const cppast::cpp_builtin_type&>(pointee).builtin_type_kind();
return builtin == cppast::cpp_char || builtin == cppast::cpp_char16
|| builtin == cppast::cpp_char32 || builtin == cppast::cpp_wchar;
}
void generate_serialize_member(std::ostream& out, const cppast::cpp_member_variable& member)
{
auto& type = cppast::remove_cv(member.type());
if (cppast::has_attribute(member, "generate::transient"))
// don't serialize transient members
return;
else if (auto attr = cppast::has_attribute(member, "generate::serialize"))
{
// generate code as specified by the attributes
out << " " << attr.value().arguments().value().as_string() << ";\n";
}
else if (type.kind() == cppast::cpp_type_kind::builtin_t)
{
// generate hypothetical member function call for builtin types
out << " s.serialize(obj." << member.name() << ");\n";
}
else if (type.kind() == cppast::cpp_type_kind::user_defined_t)
{
// generate ADL call
out << " serialize(s, obj." << member.name() << ");\n";
}
else if (is_c_string(type))
{
// generate another hypothetical member function call
out << " s.serialize_string(obj." << member.name() << ");\n";
}
else
throw std::invalid_argument("cannot serialize member " + member.name());
}
void generate_serialize(const cppast::cpp_file& file)
{
cppast::visit(file,
[](const cppast::cpp_entity& e) {
// only visit non-templated class definitions that have the attribute set
return (!cppast::is_templated(e)
&& e.kind() == cppast::cpp_entity_kind::class_t
&& cppast::is_definition(e)
&& cppast::has_attribute(e, "generate::serialize"))
// or all namespaces
|| e.kind() == cppast::cpp_entity_kind::namespace_t;
},
[](const cppast::cpp_entity& e, const cppast::visitor_info& info) {
if (e.kind() == cppast::cpp_entity_kind::class_t && !info.is_old_entity())
{
auto& class_ = static_cast<const cppast::cpp_class&>(e);
std::cout << "inline void serialize(const foo::serializer& s, const "
<< class_.name() << "& obj) {\n";
// serialize base classes
for (auto& base : class_.bases())
if (!cppast::has_attribute(base, "generate::transient"))
std::cout << " serialize(s, static_cast<const " << base.name()
<< "&>(obj));\n";
// serialize member variables
for (auto& member : class_)
{
if (member.kind() == cppast::cpp_entity_kind::member_variable_t)
generate_serialize_member(std::cout,
static_cast<
const cppast::cpp_member_variable&>(
member));
}
std::cout << "}\n\n";
}
else if (e.kind() == cppast::cpp_entity_kind::namespace_t)
{
if (info.event == cppast::visitor_info::container_entity_enter)
// open namespace
std::cout << "namespace " << e.name() << " {\n\n";
else // if (info.event == cppast::visitor_info::container_entity_exit)
// close namespace
std::cout << "}\n";
}
});
}
int main(int argc, char* argv[])
{
return example_main(argc, argv, {}, generate_serialize);
}

View file

@ -74,17 +74,13 @@ namespace cppast
}
protected:
cpp_type() noexcept : user_data_(nullptr)
{
}
cpp_type() noexcept : user_data_(nullptr) {}
private:
/// \returns The [cppast::cpp_type_kind]().
virtual cpp_type_kind do_get_kind() const noexcept = 0;
void on_insert(const cpp_type&)
{
}
void on_insert(const cpp_type&) {}
mutable std::atomic<void*> user_data_;
@ -112,9 +108,7 @@ namespace cppast
}
private:
cpp_unexposed_type(std::string name) : name_(std::move(name))
{
}
cpp_unexposed_type(std::string name) : name_(std::move(name)) {}
cpp_type_kind do_get_kind() const noexcept override
{
@ -180,9 +174,7 @@ namespace cppast
}
private:
cpp_builtin_type(cpp_builtin_type_kind kind) : kind_(kind)
{
}
cpp_builtin_type(cpp_builtin_type_kind kind) : kind_(kind) {}
cpp_type_kind do_get_kind() const noexcept override
{
@ -224,9 +216,7 @@ namespace cppast
}
private:
cpp_user_defined_type(cpp_type_ref entity) : entity_(std::move(entity))
{
}
cpp_user_defined_type(cpp_type_ref entity) : entity_(std::move(entity)) {}
cpp_type_kind do_get_kind() const noexcept override
{
@ -361,6 +351,15 @@ namespace cppast
cpp_cv cv_;
};
/// \returns The type without top-level const/volatile qualifiers.
const cpp_type& remove_cv(const cpp_type& type) noexcept;
/// \returns The type without top-level const qualifiers.
const cpp_type& remove_const(const cpp_type& type) noexcept;
/// \returns The type without top-level volatile qualifiers.
const cpp_type& remove_volatile(const cpp_type& type) noexcept;
/// A pointer to a [cppast::cpp_type]().
class cpp_pointer_type final : public cpp_type
{
@ -378,9 +377,7 @@ namespace cppast
}
private:
cpp_pointer_type(std::unique_ptr<cpp_type> pointee) : pointee_(std::move(pointee))
{
}
cpp_pointer_type(std::unique_ptr<cpp_type> pointee) : pointee_(std::move(pointee)) {}
cpp_type_kind do_get_kind() const noexcept override
{
@ -439,6 +436,9 @@ namespace cppast
cpp_reference ref_;
};
/// \returns The type as a string representation.
std::string to_string(const cpp_type& type);
/// \exclude
namespace detail
{
@ -456,9 +456,6 @@ namespace cppast
// write prefix, variadic, name, suffix
void write_type(code_generator::output& output, const cpp_type& type, std::string name,
bool is_variadic = false);
// simple to_string() for types
std::string to_string(const cpp_type& type);
} // namespace detail
} // namespace cppast

View file

@ -164,7 +164,12 @@ namespace cppast
public:
using config = libclang_compile_config;
/// \effects Creates a parser using the default logger.
libclang_parser();
/// \effects Creates a parser that will log error messages using the specified logger.
explicit libclang_parser(type_safe::object_ref<const diagnostic_logger> logger);
~libclang_parser() noexcept override;
private:

View file

@ -187,6 +187,8 @@ namespace cppast
[&](const std::string&) { return config; });
}
/// Parses all files included by `file`.
/// \effects For each [cppast::cpp_include_directive]() in file it will parse the included file.
template <class FileParser>
void resolve_includes(FileParser& parser, const cpp_file& file,
typename FileParser::config config)

View file

@ -5,6 +5,8 @@
#ifndef CPPAST_VISITOR_HPP_INCLUDED
#define CPPAST_VISITOR_HPP_INCLUDED
#include <type_traits>
#include <cppast/cpp_class.hpp>
#include <cppast/cpp_entity.hpp>
#include <cppast/cpp_entity_kind.hpp>
@ -28,83 +30,212 @@ namespace cppast
cpp_access_specifier_kind access; //< The current access specifier.
bool
last_child; //< True when the current entity is the last child of the visited parent entity.
/// True when the current entity is the last child of the visited parent entity.
/// \notes It will always be `false` for the initial entity.
bool last_child;
/// \returns `true` if the entity was not visited already.
bool is_new_entity() const noexcept
{
return event != container_entity_exit;
}
/// \returns `true` if the entity was visited already.
bool is_old_entity() const noexcept
{
return !is_new_entity();
}
};
/// A more expressive way to specify the return of a visit operation.
enum visitor_result : bool
{
/// Visit should continue.
/// \group continue
continue_visit = true,
/// \group continue
continue_visit_children = true,
/// Visit should not visit the children.
/// \notes This only happens when the event is [cppast::visitor_info::container_entity_enter]().
continue_visit_no_children = false,
/// Visit should be aborted.
/// \notes This only happens when the event is not [cppast::visitor_info::container_entity_enter]().
abort_visit = false,
};
/// \exclude
namespace detail
{
using visitor_callback_t = bool (*)(void* mem, const cpp_entity&, visitor_info info);
using visitor_predicate_t = bool (*)(const cpp_entity&);
using visitor_callback_t = bool (*)(void* mem, const cpp_entity&, visitor_info info);
struct visitor_info_void
{
};
struct visitor_info_bool : visitor_info_void
{
};
template <typename Func>
bool visitor_callback(void* mem, const cpp_entity& e, visitor_info info)
visitor_callback_t get_visitor_callback(
visitor_info_bool, decltype(std::declval<Func>()(std::declval<const cpp_entity&>(),
visitor_info{})) = true)
{
auto& func = *static_cast<Func*>(mem);
return func(e, info);
return [](void* mem, const cpp_entity& e, visitor_info info) {
auto& func = *static_cast<Func*>(mem);
return func(e, info);
};
}
template <typename Func>
visitor_callback_t get_visitor_callback(
visitor_info_void,
decltype(std::declval<Func>()(std::declval<const cpp_entity&>(), visitor_info{}),
0) = 0)
{
return [](void* mem, const cpp_entity& e, visitor_info info) {
auto& func = *static_cast<Func*>(mem);
func(e, info);
return true;
};
}
template <typename Func>
visitor_callback_t get_visitor_callback()
{
return get_visitor_callback<Func>(visitor_info_bool{});
}
bool visit(const cpp_entity& e, visitor_callback_t cb, void* functor,
cpp_access_specifier_kind cur_access, bool last_child);
} // namespace detail
/// Visits a [cppast::cpp_entity]().
/// \effects Invokes the callback for the current entity,
/// and any child entities.
/// It will pass a reference to the current entity and the [cppast::visitor_info]().
/// The return value of the callback controls the visit operation,
/// the semantic depend on the [cppast::visitor_info::event_type]().
/// Visits a [cppast::cpp_entity]() and children.
///
/// \effects It will invoke the callback - the visitor - for the current entity and children,
/// as controlled by the [cppast::visitor_result]().
/// The visitor is given a reference to the currently visited entity and the [cppast::visitor_info]().
///
/// \requires The visitor must be callable as specified and must either return `bool` or nothing.
/// If it returns nothing, a return value [cppast::visitor_result::continue_visit]() is assumed.
template <typename Func>
void visit(const cpp_entity& e, Func f)
{
detail::visit(e, &detail::visitor_callback<Func>, &f, cpp_public, false);
detail::visit(e, detail::get_visitor_callback<Func>(), &f, cpp_public, false);
}
/// Visits a [cppast::cpp_entity]().
/// \effects Invokes the callback for the current entity,
/// and child entities that match the given predicate.
/// It will pass a reference to the current entity and the [cppast::visitor_info]().
/// The return value of the callback controls the visit operation,
/// the semantic depend on the [cppast::visitor_info::event_type]().
template <typename Func, typename Predicate>
void visit(const cpp_entity& e, Predicate pred, Func f)
/// The result of a visitor filter operation.
enum class visit_filter
{
visit(e, [&](const cpp_entity& e, visitor_info& info) {
if (pred(e))
return f(e, info);
return true;
include = true, //< The entity is included.
exclude = false, //< The entity is excluded and will not be visited.
exclude_and_children =
2, //< The entity and all direct children are excluded and will not be visited.
};
namespace detail
{
using visitor_filter_t = visit_filter (*)(const cpp_entity&);
template <typename Filter>
auto invoke_visit_filter(int, Filter f, const cpp_entity& e,
cpp_access_specifier_kind access)
-> decltype(static_cast<visit_filter>(f(e, access)))
{
return static_cast<visit_filter>(f(e, access));
}
template <typename Filter>
auto invoke_visit_filter(short, Filter f, const cpp_entity& e, cpp_access_specifier_kind)
-> decltype(static_cast<visit_filter>(f(e)))
{
return static_cast<visit_filter>(f(e));
}
} // namespace detail
/// Visits a [cppast::cpp_entity]() and children that pass a given filter.
///
/// \effects It behaves like the non-filtered visit except it will only invoke the visitor for entities that match the filter.
/// The filter is a predicate that will be invoked with the current entity and access specifier first and the result converted to [cppast::visit_filter]().
/// Visit will behave accordingly.
///
/// \requires The visitor must be as specified for the other overload.
/// The filter must be a function taking a [cppast::cpp_entity]() as first parameter and optionally a [cppast::cpp_access_specifier_kind]() as second.
/// It must return a `bool` or a [cppast::visit_filter]().
template <typename Func, typename Filter>
void visit(const cpp_entity& e, Filter filter, Func f)
{
visit(e, [&](const cpp_entity& e, const visitor_info& info) -> bool {
auto result = detail::invoke_visit_filter(0, filter, e, info.access);
switch (result)
{
case visit_filter::include:
return detail::get_visitor_callback<Func>()(&f, e, info);
case visit_filter::exclude:
return continue_visit;
case visit_filter::exclude_and_children:
// exclude children if entering
if (info.event == visitor_info::container_entity_enter)
return continue_visit_no_children;
else
return continue_visit;
}
return continue_visit;
});
}
/// Generates, at compile-time, a predicate that returns true iff the
/// given entity holds one of the [cppast::cpp_entity_kind]()s specified
/// in paramaters of the blacklist template.
template <cpp_entity_kind... K>
detail::visitor_predicate_t whitelist()
/// \exclude
namespace detail
{
static_assert(sizeof...(K) > 0, "At least one entity kind should be specified");
return [](const cpp_entity& e) {
template <cpp_entity_kind... K>
bool has_one_of_kind(const cpp_entity& e)
{
static_assert(sizeof...(K) > 0, "At least one entity kind must be specified");
bool result = false;
// this ugliness avoids recursive expansion which would be required in C++11,
// otherwise known as poor man's fold expression.
(void)std::initializer_list<int>{(result = result || (K == e.kind()), 0)...};
// poor men's fold
int dummy[]{(result |= (K == e.kind()), 0)...};
(void)dummy;
return result;
}
} // namespace detail
/// Generates a blacklist visitor filter.
///
/// \returns A visitor filter that excludes all entities having one of the specified kinds.
template <cpp_entity_kind... Kinds>
detail::visitor_filter_t blacklist()
{
return [](const cpp_entity& e) {
return detail::has_one_of_kind<Kinds...>(e) ? visit_filter::exclude :
visit_filter::include;
};
}
/// Generates, at compile-time, a predicate that returns true iff the
/// given entity holds a [cppast::cpp_entity_kind]() that's different
/// from all of those specified in the paramaters of the blacklist template.
template <cpp_entity_kind... K>
detail::visitor_predicate_t blacklist()
/// Generates a blacklist visitor filter.
///
/// \returns A visitor filter that excludes all entities having one of the specified kinds and children of those entities.
template <cpp_entity_kind... Kinds>
detail::visitor_filter_t blacklist_and_children()
{
static_assert(sizeof...(K) > 0, "At least one entity kind should be specified");
return [](const cpp_entity& e) {
bool result = true;
(void)std::initializer_list<int>{(result = result && (K != e.kind()), 0)...};
return result;
return detail::has_one_of_kind<Kinds...>(e) ? visit_filter::exclude_and_children :
visit_filter::include;
};
}
/// Generates a whitelist visitor filter.
///
/// \returns A visitor filter that excludes all entities having not one of the specified kinds.
template <cpp_entity_kind... Kinds>
detail::visitor_filter_t whitelist()
{
return [](const cpp_entity& e) {
return detail::has_one_of_kind<Kinds...>(e) ? visit_filter::include :
visit_filter::exclude;
};
}
} // namespace cppast

View file

@ -103,6 +103,7 @@ set(libclang_source
libclang/variable_parser.cpp)
add_library(cppast ${detail_header} ${header} ${source} ${libclang_source})
set_target_properties(cppast PROPERTIES CXX_STANDARD 11)
target_include_directories(cppast PUBLIC ../include)
target_link_libraries(cppast PUBLIC type_safe _cppast_tiny_process _cppast_libclang)
target_compile_definitions(cppast PUBLIC
@ -115,4 +116,3 @@ endif()
if(CPPAST_ENABLE_PRECONDITION_CHECKS)
target_compile_definitions(cppast PUBLIC CPPAST_ENABLE_PRECONDITION_CHECKS)
endif()
set_target_properties(cppast PROPERTIES CXX_STANDARD 11)

View file

@ -39,7 +39,7 @@ std::string cpp_function_base::do_get_signature() const
{
std::string result = "(";
for (auto& param : parameters())
result += detail::to_string(param.type()) + ',';
result += to_string(param.type()) + ',';
if (is_variadic())
result += "...";

View file

@ -76,6 +76,39 @@ const char* cppast::to_string(cpp_builtin_type_kind kind) noexcept
return "__ups";
}
const cpp_type& cppast::remove_cv(const cpp_type& type) noexcept
{
if (type.kind() == cpp_type_kind::cv_qualified_t)
{
auto& cv = static_cast<const cpp_cv_qualified_type&>(type);
return cv.type();
}
return type;
}
const cpp_type& cppast::remove_const(const cpp_type& type) noexcept
{
if (type.kind() == cpp_type_kind::cv_qualified_t)
{
auto& cv = static_cast<const cpp_cv_qualified_type&>(type);
if (is_const(cv.cv_qualifier()))
return cv.type();
}
return type;
}
const cpp_type& cppast::remove_volatile(const cpp_type& type) noexcept
{
if (type.kind() == cpp_type_kind::cv_qualified_t)
{
auto& cv = static_cast<const cpp_cv_qualified_type&>(type);
if (is_volatile(cv.cv_qualifier()))
return cv.type();
}
return type;
}
bool detail::cpp_type_ref_predicate::operator()(const cpp_entity& e)
{
switch (e.kind())
@ -548,7 +581,7 @@ void detail::write_type(code_generator::output& output, const cpp_type& type, st
write_type_suffix(output, type);
}
std::string detail::to_string(const cpp_type& type)
std::string cppast::to_string(const cpp_type& type)
{
class to_string_generator : public code_generator
{
@ -559,13 +592,9 @@ std::string detail::to_string(const cpp_type& type)
}
private:
void do_indent() override
{
}
void do_indent() override {}
void do_unindent() override
{
}
void do_unindent() override {}
void do_write_token_seq(string_view tokens) override
{

View file

@ -266,12 +266,12 @@ namespace
while (!token_after_is(tu, file, cur, end, ";"))
end = get_next_location(tu, file, end);
}
else if (kind == CXCursor_EnumConstantDecl && !token_after_is(tu, file, cur, end, ",")
&& !token_after_is(tu, file, cur, end, ";"))
else if (kind == CXCursor_EnumConstantDecl && !token_after_is(tu, file, cur, end, ","))
{
while (!token_after_is(tu, file, cur, end, ",")
&& !token_after_is(tu, file, cur, end, ";"))
end = get_next_location(tu, file, end);
// need to support attributes
// just give up and extend the range to the range of the entire enum...
auto parent = clang_getCursorLexicalParent(cur);
end = clang_getRangeEnd(clang_getCursorExtent(parent));
}
else if (kind == CXCursor_FieldDecl || kind == CXCursor_ParmDecl
|| kind == CXCursor_NonTypeTemplateParameter

View file

@ -330,6 +330,8 @@ struct libclang_parser::impl
}
};
libclang_parser::libclang_parser() : libclang_parser(default_logger()) {}
libclang_parser::libclang_parser(type_safe::object_ref<const diagnostic_logger> logger)
: parser(logger), pimpl_(new impl)
{

View file

@ -19,9 +19,9 @@ TEST_CASE("visitor_filtered")
)";
cpp_entity_index idx;
auto file = parse(idx, "cpp_class.cpp", code);
unsigned filtered_count = 0;
auto visitor_callback = [&](const cpp_entity&, cppast::visitor_info info) {
auto file = parse(idx, "cpp_class.cpp", code);
unsigned filtered_count = 0;
auto visitor_callback = [&](const cpp_entity&, cppast::visitor_info info) {
if (info.event != cppast::visitor_info::container_entity_exit)
++filtered_count;
return true;
@ -83,4 +83,24 @@ TEST_CASE("visitor_filtered")
REQUIRE(filtered_count == all_node_count - enum_count - class_count);
}
}
SECTION("blacklist_and_children")
{
SECTION("only one kind blacklisted")
{
filtered_count = 0;
cppast::visit(*file, blacklist_and_children<cpp_entity_kind::file_t>(),
visitor_callback);
REQUIRE(filtered_count == 0);
}
SECTION("many kinds blacklisted")
{
filtered_count = 0;
cppast::visit(*file,
blacklist_and_children<cpp_entity_kind::enum_t,
cpp_entity_kind::class_t>(),
visitor_callback);
REQUIRE(filtered_count == all_node_count - enum_count - class_count);
}
}
}

View file

@ -4,5 +4,4 @@
add_executable(cppast_tool main.cpp)
target_link_libraries(cppast_tool PUBLIC cppast cxxopts)
set_target_properties(cppast_tool PROPERTIES CXX_STANDARD 11
OUTPUT_NAME cppast)
set_target_properties(cppast_tool PROPERTIES CXX_STANDARD 11 OUTPUT_NAME cppast)