Improve visit facility
This commit is contained in:
parent
33d2523893
commit
d102d769c1
3 changed files with 198 additions and 50 deletions
|
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue