diff --git a/example/ast_printer.cpp b/example/ast_printer.cpp index b6acb0c..a06eb31 100644 --- a/example/ast_printer.cpp +++ b/example/ast_printer.cpp @@ -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 }); } diff --git a/include/cppast/visitor.hpp b/include/cppast/visitor.hpp index 889c194..9f659ce 100644 --- a/include/cppast/visitor.hpp +++ b/include/cppast/visitor.hpp @@ -5,6 +5,8 @@ #ifndef CPPAST_VISITOR_HPP_INCLUDED #define CPPAST_VISITOR_HPP_INCLUDED +#include + #include #include #include @@ -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 - bool visitor_callback(void* mem, const cpp_entity& e, visitor_info info) + visitor_callback_t get_visitor_callback( + visitor_info_bool, decltype(std::declval()(std::declval(), + visitor_info{})) = true) { - auto& func = *static_cast(mem); - return func(e, info); + return [](void* mem, const cpp_entity& e, visitor_info info) { + auto& func = *static_cast(mem); + return func(e, info); + }; + } + + template + visitor_callback_t get_visitor_callback( + visitor_info_void, + decltype(std::declval()(std::declval(), visitor_info{}), + 0) = 0) + { + return [](void* mem, const cpp_entity& e, visitor_info info) { + auto& func = *static_cast(mem); + func(e, info); + return true; + }; + } + + template + visitor_callback_t get_visitor_callback() + { + return get_visitor_callback(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 void visit(const cpp_entity& e, Func f) { - detail::visit(e, &detail::visitor_callback, &f, cpp_public, false); + detail::visit(e, detail::get_visitor_callback(), &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 - 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 + auto invoke_visit_filter(int, Filter f, const cpp_entity& e, + cpp_access_specifier_kind access) + -> decltype(static_cast(f(e, access))) + { + return static_cast(f(e, access)); + } + + template + auto invoke_visit_filter(short, Filter f, const cpp_entity& e, cpp_access_specifier_kind) + -> decltype(static_cast(f(e))) + { + return static_cast(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 + 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()(&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 - 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 + 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{(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 + detail::visitor_filter_t blacklist() + { + return [](const cpp_entity& e) { + return detail::has_one_of_kind(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 - 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 + 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{(result = result && (K != e.kind()), 0)...}; - return result; + return detail::has_one_of_kind(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 + detail::visitor_filter_t whitelist() + { + return [](const cpp_entity& e) { + return detail::has_one_of_kind(e) ? visit_filter::include : + visit_filter::exclude; }; } } // namespace cppast diff --git a/test/visitor.cpp b/test/visitor.cpp index df4ed00..fb9abf3 100644 --- a/test/visitor.cpp +++ b/test/visitor.cpp @@ -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(), + visitor_callback); + REQUIRE(filtered_count == 0); + } + + SECTION("many kinds blacklisted") + { + filtered_count = 0; + cppast::visit(*file, + blacklist_and_children(), + visitor_callback); + REQUIRE(filtered_count == all_node_count - enum_count - class_count); + } + } }