diff --git a/include/cppast/visitor.hpp b/include/cppast/visitor.hpp index b4ce2ec..6195dbc 100644 --- a/include/cppast/visitor.hpp +++ b/include/cppast/visitor.hpp @@ -5,10 +5,11 @@ #ifndef CPPAST_VISITOR_HPP_INCLUDED #define CPPAST_VISITOR_HPP_INCLUDED +#include +#include + namespace cppast { - class cpp_entity; - /// Information about the state of a visit operation. struct visitor_info { @@ -31,7 +32,8 @@ namespace cppast /// \exclude 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&); template bool visitor_callback(void* mem, const cpp_entity& e, visitor_info info) @@ -54,6 +56,52 @@ namespace cppast { detail::visit(e, &detail::visitor_callback, &f, 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) + { + visit(e, [&](const cpp_entity& e, visitor_info& info) { + if (pred(e)) + return f(e, info); + return true; + }); + } + + /// 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() + { + static_assert(sizeof...(K) > 0, "At least one entity kind should be specified"); + return [](const cpp_entity& e) { + 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)...}; + return result; + }; + } + + /// 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() + { + 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; + }; + } } // namespace cppast #endif // CPPAST_VISITOR_HPP_INCLUDED diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5610510..34c651b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,7 +25,8 @@ set(tests cpp_static_assert.cpp cpp_template_parameter.cpp cpp_type_alias.cpp - cpp_variable.cpp) + cpp_variable.cpp + visitor.cpp) add_executable(cppast_test test.cpp test_parser.hpp ${tests}) target_include_directories(cppast_test PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/test/cpp_class.cpp b/test/cpp_class.cpp index 93a0a0c..69ed302 100644 --- a/test/cpp_class.cpp +++ b/test/cpp_class.cpp @@ -301,3 +301,4 @@ struct g }); REQUIRE(count == 12u); } + diff --git a/test/visitor.cpp b/test/visitor.cpp new file mode 100644 index 0000000..df4ed00 --- /dev/null +++ b/test/visitor.cpp @@ -0,0 +1,86 @@ +#include +using namespace cppast; + +#include "test_parser.hpp" +#include + +TEST_CASE("visitor_filtered") +{ + auto code = R"( + namespace the_ns { + class foo { + enum inner_enum {}; + class bar {}; + }; + class one {}; class two {}; class three {}; + enum quaz {}; + } + enum outer {}; + )"; + + 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) { + if (info.event != cppast::visitor_info::container_entity_exit) + ++filtered_count; + return true; + }; + + constexpr auto all_node_count = 10, enum_count = 3, class_count = 5; + + SECTION("all nodes are visited") + { + filtered_count = 0; + cppast::visit(*file, [](const cpp_entity&) { return true; }, visitor_callback); + REQUIRE(filtered_count == all_node_count); + } + + SECTION("filtered callback on both enter and exit") + { + filtered_count = 0; + cppast::visit(*file, [](const cpp_entity&) { return true; }, + [&](const cpp_entity&, cppast::visitor_info info) { + (void)info; + filtered_count++; + return true; + }); + REQUIRE(filtered_count == all_node_count * 2); + } + + SECTION("whitelist") + { + SECTION("only one kind whitelisted") + { + filtered_count = 0; + cppast::visit(*file, whitelist(), visitor_callback); + REQUIRE(filtered_count == enum_count); + } + + SECTION("many kinds whitelisted") + { + filtered_count = 0; + cppast::visit(*file, whitelist(), + visitor_callback); + REQUIRE(filtered_count == enum_count + class_count); + } + } + + SECTION("blacklist") + { + SECTION("only one kind blacklisted") + { + filtered_count = 0; + cppast::visit(*file, blacklist(), visitor_callback); + REQUIRE(filtered_count == all_node_count - 1); + } + + SECTION("many kinds blacklisted") + { + filtered_count = 0; + cppast::visit(*file, blacklist(), + visitor_callback); + REQUIRE(filtered_count == all_node_count - enum_count - class_count); + } + } +}