Improve visit facility

This commit is contained in:
Jonathan Müller 2017-10-31 18:03:44 +01:00
commit d102d769c1
3 changed files with 198 additions and 50 deletions

View file

@ -24,8 +24,6 @@ void print_ast(const cppast::cpp_file& file)
}
else // if (info.event == cppast::visitor_info::leaf_entity) // a non-container entity
std::cout << prefix << "'" << e.name() << "' - " << cppast::to_string(e.kind()) << '\n';
return true; // continue with visit
});
}

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,211 @@ 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;
}
});
}
/// 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

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