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
|
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
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue