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 else // if (info.event == cppast::visitor_info::leaf_entity) // a non-container entity
std::cout << prefix << "'" << e.name() << "' - " << cppast::to_string(e.kind()) << '\n'; 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 #ifndef CPPAST_VISITOR_HPP_INCLUDED
#define CPPAST_VISITOR_HPP_INCLUDED #define CPPAST_VISITOR_HPP_INCLUDED
#include <type_traits>
#include <cppast/cpp_class.hpp> #include <cppast/cpp_class.hpp>
#include <cppast/cpp_entity.hpp> #include <cppast/cpp_entity.hpp>
#include <cppast/cpp_entity_kind.hpp> #include <cppast/cpp_entity_kind.hpp>
@ -28,83 +30,211 @@ namespace cppast
cpp_access_specifier_kind access; //< The current access specifier. cpp_access_specifier_kind access; //< The current access specifier.
bool /// True when the current entity is the last child of the visited parent entity.
last_child; //< True when the current entity is the last child of the visited parent entity.
/// \notes It will always be `false` for the initial 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 /// \exclude
namespace detail namespace detail
{ {
using visitor_callback_t = bool (*)(void* mem, const cpp_entity&, visitor_info info); using visitor_callback_t = bool (*)(void* mem, const cpp_entity&, visitor_info info);
using visitor_predicate_t = bool (*)(const cpp_entity&);
struct visitor_info_void
{
};
struct visitor_info_bool : visitor_info_void
{
};
template <typename Func> 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)
{ {
return [](void* mem, const cpp_entity& e, visitor_info info) {
auto& func = *static_cast<Func*>(mem); auto& func = *static_cast<Func*>(mem);
return func(e, info); 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, bool visit(const cpp_entity& e, visitor_callback_t cb, void* functor,
cpp_access_specifier_kind cur_access, bool last_child); cpp_access_specifier_kind cur_access, bool last_child);
} // namespace detail } // namespace detail
/// Visits a [cppast::cpp_entity](). /// Visits a [cppast::cpp_entity]() and children.
/// \effects Invokes the callback for the current entity, ///
/// and any child entities. /// \effects It will invoke the callback - the visitor - for the current entity and children,
/// It will pass a reference to the current entity and the [cppast::visitor_info](). /// as controlled by the [cppast::visitor_result]().
/// The return value of the callback controls the visit operation, /// The visitor is given a reference to the currently visited entity and the [cppast::visitor_info]().
/// the semantic depend on the [cppast::visitor_info::event_type](). ///
/// \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> template <typename Func>
void visit(const cpp_entity& e, Func f) 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](). /// The result of a visitor filter operation.
/// \effects Invokes the callback for the current entity, enum class visit_filter
/// 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)
{ {
visit(e, [&](const cpp_entity& e, visitor_info& info) { include = true, //< The entity is included.
if (pred(e)) exclude = false, //< The entity is excluded and will not be visited.
return f(e, info); exclude_and_children =
return true; 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 /// \exclude
/// given entity holds one of the [cppast::cpp_entity_kind]()s specified namespace detail
/// in paramaters of the blacklist template.
template <cpp_entity_kind... K>
detail::visitor_predicate_t whitelist()
{ {
static_assert(sizeof...(K) > 0, "At least one entity kind should be specified"); template <cpp_entity_kind... K>
return [](const cpp_entity& e) { 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; bool result = false;
// this ugliness avoids recursive expansion which would be required in C++11,
// otherwise known as poor man's fold expression. // poor men's fold
(void)std::initializer_list<int>{(result = result || (K == e.kind()), 0)...}; int dummy[]{(result |= (K == e.kind()), 0)...};
(void)dummy;
return result; 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 /// Generates a blacklist visitor filter.
/// given entity holds a [cppast::cpp_entity_kind]() that's different ///
/// from all of those specified in the paramaters of the blacklist template. /// \returns A visitor filter that excludes all entities having one of the specified kinds and children of those entities.
template <cpp_entity_kind... K> template <cpp_entity_kind... Kinds>
detail::visitor_predicate_t blacklist() 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) { return [](const cpp_entity& e) {
bool result = true; return detail::has_one_of_kind<Kinds...>(e) ? visit_filter::exclude_and_children :
(void)std::initializer_list<int>{(result = result && (K != e.kind()), 0)...}; visit_filter::include;
return result; };
}
/// 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 } // namespace cppast

View file

@ -83,4 +83,24 @@ TEST_CASE("visitor_filtered")
REQUIRE(filtered_count == all_node_count - enum_count - class_count); 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);
}
}
} }