From 6f128da87f1355df2591b8b42dab5a8c081b375e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Mon, 30 Oct 2017 15:48:50 +0100 Subject: [PATCH 01/13] Add default constructor to libclang_parser --- include/cppast/libclang_parser.hpp | 5 +++++ include/cppast/parser.hpp | 2 ++ src/libclang/libclang_parser.cpp | 2 ++ 3 files changed, 9 insertions(+) diff --git a/include/cppast/libclang_parser.hpp b/include/cppast/libclang_parser.hpp index 5b02037..90eba16 100644 --- a/include/cppast/libclang_parser.hpp +++ b/include/cppast/libclang_parser.hpp @@ -164,7 +164,12 @@ namespace cppast public: using config = libclang_compile_config; + /// \effects Creates a parser using the default logger. + libclang_parser(); + + /// \effects Creates a parser that will log error messages using the specified logger. explicit libclang_parser(type_safe::object_ref logger); + ~libclang_parser() noexcept override; private: diff --git a/include/cppast/parser.hpp b/include/cppast/parser.hpp index 84badba..67cbcd9 100644 --- a/include/cppast/parser.hpp +++ b/include/cppast/parser.hpp @@ -187,6 +187,8 @@ namespace cppast [&](const std::string&) { return config; }); } + /// Parses all files included by `file`. + /// \effects For each [cppast::cpp_include_directive]() in file it will parse the included file. template void resolve_includes(FileParser& parser, const cpp_file& file, typename FileParser::config config) diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index 65ab58c..e40abe9 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -330,6 +330,8 @@ struct libclang_parser::impl } }; +libclang_parser::libclang_parser() : libclang_parser(default_logger()) {} + libclang_parser::libclang_parser(type_safe::object_ref logger) : parser(logger), pimpl_(new impl) { From 6af95dde8e2af4399aab47ed445dbf85c06e79a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Mon, 30 Oct 2017 16:21:13 +0100 Subject: [PATCH 02/13] Add first example --- CMakeLists.txt | 10 +++++++ example/CMakeLists.txt | 10 +++++++ example/ast_printer.cpp | 65 +++++++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 1 + test/CMakeLists.txt | 1 - tool/CMakeLists.txt | 3 +- 6 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 example/CMakeLists.txt create mode 100644 example/ast_printer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 40eff0e..b6d37b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ option(CPPAST_ENABLE_ASSERTIONS "whether or not to enable internal assertions fo option(CPPAST_ENABLE_PRECONDITION_CHECKS "whether or not to enable precondition checks" ON) option(CPPAST_BUILD_TEST "whether or not to build the tests" ON) +option(CPPAST_BUILD_EXAMPLE "whether or not to build the examples" ON) option(CPPAST_BUILD_TOOL "whether or not to build the tool" ON) option(BUILD_TESTING "build test" OFF) # The ctest variable for building tests @@ -24,6 +25,12 @@ else() set(build_test OFF) endif() +if(${CPPAST_BUILD_EXAMPLE} OR (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)) + set(build_example ON) +else() + set(build_example OFF) +endif() + if(${CPPAST_BUILD_TOOL} OR (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)) set(build_tool ON) else() @@ -43,6 +50,9 @@ add_subdirectory(src) if(${build_test}) add_subdirectory(test) endif() +if(${build_example}) + add_subdirectory(example) +endif() if(${build_tool}) add_subdirectory(tool) endif() diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..4b5f547 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (C) 2017 Jonathan Müller +# This file is subject to the license terms in the LICENSE file +# found in the top-level directory of this distribution. + +function(_cppast_example name) + add_executable(cppast_example_${name} ${name}.cpp) + target_link_libraries(cppast_example_${name} PUBLIC cppast) +endfunction() + +_cppast_example(ast_printer) diff --git a/example/ast_printer.cpp b/example/ast_printer.cpp new file mode 100644 index 0000000..5346563 --- /dev/null +++ b/example/ast_printer.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +/// \file +/// This is a very primitive version of the cppast tool. + +#include + +#include // libclang_compilation_database, libclang_parser, etc. +#include // visit() + +int main(int argc, char* argv[]) +{ + if (argc != 2) + { + std::cerr << "usage: " << argv[0] << " \n"; + return 1; + } + else + { + cppast::libclang_compilation_database database(argv[1]); // the compilation database + cppast::cpp_entity_index index; // only needed for cross referencing in the AST + + // simple_file_parser allows parsing multiple files and stores the results for us + cppast::simple_file_parser parser(type_safe::ref(index)); + try + { + cppast::parse_database(parser, database); // parse all files in the database + } + catch (cppast::libclang_error& ex) + { + std::cerr << "fatal libclang error: " << ex.what() << '\n'; + return 1; + } + + if (parser.error()) + // a non-fatal parse error + // error has been logged to stderr + return 1; + + for (auto& file : parser.files()) // for each file in the project + { + std::string prefix; + // visit each entity in the file + cppast::visit(file, [&](const cppast::cpp_entity& e, cppast::visitor_info info) { + if (info.event + == cppast::visitor_info::container_entity_exit) // exiting an old container + prefix.pop_back(); + else if (info.event == cppast::visitor_info::container_entity_enter) + // entering a new container + { + std::cout << prefix << "'" << e.name() << "' - " << cppast::to_string(e.kind()) + << '\n'; + prefix += "\t"; + } + 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/src/CMakeLists.txt b/src/CMakeLists.txt index c45d7bb..26cded2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -103,6 +103,7 @@ set(libclang_source libclang/variable_parser.cpp) add_library(cppast ${detail_header} ${header} ${source} ${libclang_source}) +set_target_properties(cppast PROPERTIES CXX_STANDARD 11) target_include_directories(cppast PUBLIC ../include) target_link_libraries(cppast PUBLIC type_safe _cppast_tiny_process _cppast_libclang) target_compile_definitions(cppast PUBLIC diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 281a4b1..6c75862 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,7 +36,6 @@ set(tests add_executable(cppast_test test.cpp test_parser.hpp ${tests}) target_include_directories(cppast_test PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(cppast_test PUBLIC cppast) -set_target_properties(cppast_test PROPERTIES CXX_STANDARD 11) enable_testing() add_test(NAME test COMMAND cppast_test) diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt index 8f3aaf0..75661da 100644 --- a/tool/CMakeLists.txt +++ b/tool/CMakeLists.txt @@ -4,5 +4,4 @@ add_executable(cppast_tool main.cpp) target_link_libraries(cppast_tool PUBLIC cppast cxxopts) -set_target_properties(cppast_tool PROPERTIES CXX_STANDARD 11 - OUTPUT_NAME cppast) +set_target_properties(cppast_tool PROPERTIES OUTPUT_NAME cppast) From 33d25238937cc7656d3867fece2b0ad46d9134b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Mon, 30 Oct 2017 16:30:34 +0100 Subject: [PATCH 03/13] Extract common main function of examples --- example/CMakeLists.txt | 2 +- example/ast_printer.cpp | 76 ++++++++++++-------------------------- example/example_parser.hpp | 51 +++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 54 deletions(-) create mode 100644 example/example_parser.hpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 4b5f547..867bdd8 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -3,7 +3,7 @@ # found in the top-level directory of this distribution. function(_cppast_example name) - add_executable(cppast_example_${name} ${name}.cpp) + add_executable(cppast_example_${name} ${name}.cpp example_parser.hpp) target_link_libraries(cppast_example_${name} PUBLIC cppast) endfunction() diff --git a/example/ast_printer.cpp b/example/ast_printer.cpp index 5346563..b6acb0c 100644 --- a/example/ast_printer.cpp +++ b/example/ast_printer.cpp @@ -5,61 +5,31 @@ /// \file /// This is a very primitive version of the cppast tool. -#include +#include // visit() -#include // libclang_compilation_database, libclang_parser, etc. -#include // visit() +#include "example_parser.hpp" + +void print_ast(const cppast::cpp_file& file) +{ + std::string prefix; + // visit each entity in the file + cppast::visit(file, [&](const cppast::cpp_entity& e, cppast::visitor_info info) { + if (info.event == cppast::visitor_info::container_entity_exit) // exiting an old container + prefix.pop_back(); + else if (info.event == cppast::visitor_info::container_entity_enter) + // entering a new container + { + std::cout << prefix << "'" << e.name() << "' - " << cppast::to_string(e.kind()) << '\n'; + prefix += "\t"; + } + 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 + }); +} int main(int argc, char* argv[]) { - if (argc != 2) - { - std::cerr << "usage: " << argv[0] << " \n"; - return 1; - } - else - { - cppast::libclang_compilation_database database(argv[1]); // the compilation database - cppast::cpp_entity_index index; // only needed for cross referencing in the AST - - // simple_file_parser allows parsing multiple files and stores the results for us - cppast::simple_file_parser parser(type_safe::ref(index)); - try - { - cppast::parse_database(parser, database); // parse all files in the database - } - catch (cppast::libclang_error& ex) - { - std::cerr << "fatal libclang error: " << ex.what() << '\n'; - return 1; - } - - if (parser.error()) - // a non-fatal parse error - // error has been logged to stderr - return 1; - - for (auto& file : parser.files()) // for each file in the project - { - std::string prefix; - // visit each entity in the file - cppast::visit(file, [&](const cppast::cpp_entity& e, cppast::visitor_info info) { - if (info.event - == cppast::visitor_info::container_entity_exit) // exiting an old container - prefix.pop_back(); - else if (info.event == cppast::visitor_info::container_entity_enter) - // entering a new container - { - std::cout << prefix << "'" << e.name() << "' - " << cppast::to_string(e.kind()) - << '\n'; - prefix += "\t"; - } - 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 - }); - } - } + return example_main(argc, argv, {}, &print_ast); } diff --git a/example/example_parser.hpp b/example/example_parser.hpp new file mode 100644 index 0000000..ab200a7 --- /dev/null +++ b/example/example_parser.hpp @@ -0,0 +1,51 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#ifndef CPPAST_EXAMPLE_PARSER_HPP_INCLUDED +#define CPPAST_EXAMPLE_PARSER_HPP_INCLUDED + +#include + +#include + +// reads the database directory from the command line argument +// parses all files in that directory +// and invokes the callback for each of them +template +int example_main(int argc, char* argv[], const cppast::cpp_entity_index& index, Callback cb) +{ + if (argc != 2) + { + std::cerr << "usage: " << argv[0] << " \n"; + return 1; + } + else + { + cppast::libclang_compilation_database database(argv[1]); // the compilation database + + // simple_file_parser allows parsing multiple files and stores the results for us + cppast::simple_file_parser parser(type_safe::ref(index)); + try + { + cppast::parse_database(parser, database); // parse all files in the database + } + catch (cppast::libclang_error& ex) + { + std::cerr << "fatal libclang error: " << ex.what() << '\n'; + return 1; + } + + if (parser.error()) + // a non-fatal parse error + // error has been logged to stderr + return 1; + + for (auto& file : parser.files()) + cb(file); + } + + return 0; +} + +#endif // CPPAST_EXAMPLE_PARSER_HPP_INCLUDED From d102d769c16c24cdb49033b2769f3f03b46d4aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Tue, 31 Oct 2017 18:03:44 +0100 Subject: [PATCH 04/13] Improve visit facility --- example/ast_printer.cpp | 2 - include/cppast/visitor.hpp | 220 +++++++++++++++++++++++++++++-------- test/visitor.cpp | 26 ++++- 3 files changed, 198 insertions(+), 50 deletions(-) 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); + } + } } From 6a54fdd27787780736e3be80b8145faaa961c41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Tue, 31 Oct 2017 18:05:27 +0100 Subject: [PATCH 05/13] Add documentation generator example --- example/CMakeLists.txt | 1 + example/documentation_generator.cpp | 160 ++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 example/documentation_generator.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 867bdd8..928bdfb 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -8,3 +8,4 @@ function(_cppast_example name) endfunction() _cppast_example(ast_printer) +_cppast_example(documentation_generator) diff --git a/example/documentation_generator.cpp b/example/documentation_generator.cpp new file mode 100644 index 0000000..b1278d9 --- /dev/null +++ b/example/documentation_generator.cpp @@ -0,0 +1,160 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +/// \file +/// A primitive documentation generator. + +#include // code_generator, generate_code() +#include // visit() + +#include "example_parser.hpp" + +// don't show in code generation +bool is_excluded_synopsis(const cppast::cpp_entity& e, cppast::cpp_access_specifier_kind access) +{ + // exclude privates and those marked for exclusion + return access == cppast::cpp_private || cppast::has_attribute(e, "documentation::exclude"); +} + +// don't show in documentation +bool is_excluded_documentation(const cppast::cpp_entity& e, + const cppast::cpp_access_specifier_kind access) +{ + // exclude uninteresting entities + return e.kind() == cppast::cpp_entity_kind::access_specifier_t + || e.kind() == cppast::cpp_entity_kind::using_declaration_t + || e.kind() == cppast::cpp_entity_kind::using_directive_t + || e.kind() == cppast::cpp_entity_kind::static_assert_t + || e.kind() == cppast::cpp_entity_kind::include_directive_t + // and all excluded in synopsis + || is_excluded_synopsis(e, access); +} + +// generates synopsis of an entity +std::string generate_synopsis(const cppast::cpp_entity& e) +{ + // the generator for the synopsis + class synopsis_generator final : public cppast::code_generator + { + public: + // get the resulting string + std::string result() + { + return std::move(str_); + } + + private: + // whether or not the entity is the main entity that is being documented + bool is_main_entity(const cppast::cpp_entity& e) + { + if (cppast::is_templated(e) || cppast::is_friended(e)) + // need to ask the real entity + return is_main_entity(e.parent().value()); + else + return &e == &this->main_entity(); + } + + // get some nicer formatting + cppast::formatting do_get_formatting() const override + { + return cppast::formatting_flags::brace_nl | cppast::formatting_flags::comma_ws + | cppast::formatting_flags::operator_ws; + } + + // calculate generation options + generation_options do_get_options(const cppast::cpp_entity& e, + cppast::cpp_access_specifier_kind access) override + { + if (is_excluded_synopsis(e, access)) + return cppast::code_generator::exclude; + else if (!is_main_entity(e)) + // only generation declaration for the non-documented entity + return cppast::code_generator::declaration; + else + // default options + return {}; + } + + // update indendation level + void do_indent() override + { + ++indent_; + } + void do_unindent() override + { + if (indent_) + --indent_; + } + + // write specified tokens + // need to change indentation for each newline + void do_write_token_seq(cppast::string_view tokens) override + { + if (was_newline_) + { + str_ += std::string(indent_ * 2u, ' '); + was_newline_ = false; + } + + str_ += tokens.c_str(); + } + + // write + remember newline + void do_write_newline() override + { + str_ += "\n"; + was_newline_ = true; + } + + std::string str_; + unsigned indent_ = 0; + bool was_newline_ = false; + } generator; + cppast::generate_code(generator, e); + return generator.result(); +} + +void generate_documentation(const cppast::cpp_file& file) +{ + // visit each entity + cppast::visit(file, + [](const cppast::cpp_entity& e, cppast::cpp_access_specifier_kind access) { + if (is_excluded_documentation(e, access)) + // exclude this and all children + return cppast::visit_filter::exclude_and_children; + else if (cppast::is_templated(e) || cppast::is_friended(e)) + // continue on with children for a dummy entity + return cppast::visit_filter::exclude; + else + return cppast::visit_filter::include; + }, + [](const cppast::cpp_entity& e, const cppast::visitor_info& info) { + if (info.is_old_entity()) + // already done + return; + + // print name + std::cout << "## " << cppast::to_string(e.kind()) << " '" << e.name() + << "'\n"; + std::cout << '\n'; + + // print synopsis + std::cout << "```\n"; + std::cout << generate_synopsis(e); + std::cout << "```\n\n"; + + // print documentation comment + if (e.comment()) + std::cout << e.comment().value() << '\n'; + + // print separator + std::cout << "\n---\n\n"; + }); + std::cout << "\n\n"; +} + +int main(int argc, char* argv[]) +{ + return example_main(argc, argv, {}, &generate_documentation); +} From b69e7bfacd629ee4c25ffb873a80f49518deb43c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Tue, 31 Oct 2017 18:28:18 +0100 Subject: [PATCH 06/13] Add enum_to_string example --- example/CMakeLists.txt | 1 + example/enum_to_string.cpp | 61 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 example/enum_to_string.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 928bdfb..a831176 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -9,3 +9,4 @@ endfunction() _cppast_example(ast_printer) _cppast_example(documentation_generator) +_cppast_example(enum_to_string) diff --git a/example/enum_to_string.cpp b/example/enum_to_string.cpp new file mode 100644 index 0000000..ec1428a --- /dev/null +++ b/example/enum_to_string.cpp @@ -0,0 +1,61 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +/// \file +/// Generates enum `to_string()` code. + +#include + +#include // cpp_enum +#include // visit() + +#include "example_parser.hpp" + +void generate_to_string(const cppast::cpp_file& file) +{ + cppast::visit(file, + [](const cppast::cpp_entity& e) { + // only visit enums that have the attribute set + return (e.kind() == cppast::cpp_entity_kind::enum_t + && cppast::has_attribute(e, "generate::to_string")) + // or all namespaces + || e.kind() == cppast::cpp_entity_kind::namespace_t; + }, + [](const cppast::cpp_entity& e, const cppast::visitor_info& info) { + if (e.kind() == cppast::cpp_entity_kind::enum_t && !info.is_old_entity()) + { + // a new enum, generate to string function + auto& enum_ = static_cast(e); + + // write function header + std::cout << "const char* to_string(const " << enum_.name() << "& e) {\n"; + + // generate switch + std::cout << " switch (e) {\n"; + for (const auto& enumerator : enum_) + { + std::cout << " case " << enum_.name() << "::" << enumerator.name() + << ":\n"; + std::cout << " return \"" << enumerator.name() << "\";\n"; + } + std::cout << " }\n"; + + std::cout << "}\n\n"; + } + else if (e.kind() == cppast::cpp_entity_kind::namespace_t) + { + if (info.event == cppast::visitor_info::container_entity_enter) + // open namespace + std::cout << "namespace " << e.name() << " {\n\n"; + else // if (info.event == cppast::visitor_info::container_entity_exit) + // close namespace + std::cout << "}\n"; + } + }); +} + +int main(int argc, char* argv[]) +{ + return example_main(argc, argv, {}, &generate_to_string); +} From 9329fc75ec083880f6f3914f7c3dd7d7b5ec37d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Tue, 31 Oct 2017 19:28:24 +0100 Subject: [PATCH 07/13] Add serialization example --- example/CMakeLists.txt | 1 + example/enum_to_string.cpp | 3 +- example/example_parser.hpp | 7 ++- example/serialization.cpp | 115 ++++++++++++++++++++++++++++++++++++ include/cppast/cpp_type.hpp | 33 +++++------ src/cpp_type.cpp | 41 +++++++++++-- 6 files changed, 174 insertions(+), 26 deletions(-) create mode 100644 example/serialization.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index a831176..49b01a2 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -10,3 +10,4 @@ endfunction() _cppast_example(ast_printer) _cppast_example(documentation_generator) _cppast_example(enum_to_string) +_cppast_example(serialization) diff --git a/example/enum_to_string.cpp b/example/enum_to_string.cpp index ec1428a..77065ff 100644 --- a/example/enum_to_string.cpp +++ b/example/enum_to_string.cpp @@ -16,8 +16,9 @@ void generate_to_string(const cppast::cpp_file& file) { cppast::visit(file, [](const cppast::cpp_entity& e) { - // only visit enums that have the attribute set + // only visit enum definitions that have the attribute set return (e.kind() == cppast::cpp_entity_kind::enum_t + && cppast::is_definition(e) && cppast::has_attribute(e, "generate::to_string")) // or all namespaces || e.kind() == cppast::cpp_entity_kind::namespace_t; diff --git a/example/example_parser.hpp b/example/example_parser.hpp index ab200a7..b91c12c 100644 --- a/example/example_parser.hpp +++ b/example/example_parser.hpp @@ -13,7 +13,7 @@ // parses all files in that directory // and invokes the callback for each of them template -int example_main(int argc, char* argv[], const cppast::cpp_entity_index& index, Callback cb) +int example_main(int argc, char* argv[], const cppast::cpp_entity_index& index, Callback cb) try { if (argc != 2) { @@ -47,5 +47,10 @@ int example_main(int argc, char* argv[], const cppast::cpp_entity_index& index, return 0; } +catch (std::exception& ex) +{ + std::cerr << ex.what() << '\n'; + return 1; +} #endif // CPPAST_EXAMPLE_PARSER_HPP_INCLUDED diff --git a/example/serialization.cpp b/example/serialization.cpp new file mode 100644 index 0000000..af07bcf --- /dev/null +++ b/example/serialization.cpp @@ -0,0 +1,115 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +/// \file +/// Serialization code generation. + +#include + +#include +#include +#include +#include + +#include "example_parser.hpp" + +// whether or not a type is a C string, i.e. char pointer +bool is_c_string(const cppast::cpp_type& type) +{ + if (type.kind() != cppast::cpp_type_kind::pointer_t) + return false; + + auto& pointee = cppast::remove_cv(static_cast(type).pointee()); + if (pointee.kind() != cppast::cpp_type_kind::builtin_t) + return false; + + auto builtin = static_cast(pointee).builtin_type_kind(); + return builtin == cppast::cpp_char || builtin == cppast::cpp_char16 + || builtin == cppast::cpp_char32 || builtin == cppast::cpp_wchar; +} + +void generate_serialize_member(std::ostream& out, const cppast::cpp_member_variable& member) +{ + auto& type = cppast::remove_cv(member.type()); + + if (cppast::has_attribute(member, "generate::transient")) + // don't serialize transient members + return; + else if (auto attr = cppast::has_attribute(member, "generate::serialize")) + { + // generate code as specified by the attributes + out << " " << attr.value().arguments().value().as_string() << ";\n"; + } + else if (type.kind() == cppast::cpp_type_kind::builtin_t) + { + // generate hypothetical member function call for builtin types + out << " s.serialize(obj." << member.name() << ");\n"; + } + else if (type.kind() == cppast::cpp_type_kind::user_defined_t) + { + // generate ADL call + out << " serialize(s, obj." << member.name() << ");\n"; + } + else if (is_c_string(type)) + { + // generate another hypothetical member function call + out << " s.serialize_string(obj." << member.name() << ");\n"; + } + else + throw std::invalid_argument("cannot serialize member " + member.name()); +} + +void generate_serialize(const cppast::cpp_file& file) +{ + cppast::visit(file, + [](const cppast::cpp_entity& e) { + // only visit non-templated class definitions that have the attribute set + return (!cppast::is_templated(e) + && e.kind() == cppast::cpp_entity_kind::class_t + && cppast::is_definition(e) + && cppast::has_attribute(e, "generate::serialize")) + // or all namespaces + || e.kind() == cppast::cpp_entity_kind::namespace_t; + }, + [](const cppast::cpp_entity& e, const cppast::visitor_info& info) { + if (e.kind() == cppast::cpp_entity_kind::class_t && !info.is_old_entity()) + { + auto& class_ = static_cast(e); + + std::cout << "void serialize(const foo::serializer& s, const " + << class_.name() << "& obj) {\n"; + + // serialize base classes + for (auto& base : class_.bases()) + std::cout << " serialize(s, static_cast(obj));\n"; + + // serialize member variables + for (auto& member : class_) + { + if (member.kind() == cppast::cpp_entity_kind::member_variable_t) + generate_serialize_member(std::cout, + static_cast< + const cppast::cpp_member_variable&>( + member)); + } + + std::cout << "}\n\n"; + } + else if (e.kind() == cppast::cpp_entity_kind::namespace_t) + { + if (info.event == cppast::visitor_info::container_entity_enter) + // open namespace + std::cout << "namespace " << e.name() << " {\n\n"; + else // if (info.event == cppast::visitor_info::container_entity_exit) + // close namespace + std::cout << "}\n"; + } + }); +} + +int main(int argc, char* argv[]) +{ + return example_main(argc, argv, {}, generate_serialize); +} diff --git a/include/cppast/cpp_type.hpp b/include/cppast/cpp_type.hpp index 8325baa..9bf3e02 100644 --- a/include/cppast/cpp_type.hpp +++ b/include/cppast/cpp_type.hpp @@ -74,17 +74,13 @@ namespace cppast } protected: - cpp_type() noexcept : user_data_(nullptr) - { - } + cpp_type() noexcept : user_data_(nullptr) {} private: /// \returns The [cppast::cpp_type_kind](). virtual cpp_type_kind do_get_kind() const noexcept = 0; - void on_insert(const cpp_type&) - { - } + void on_insert(const cpp_type&) {} mutable std::atomic user_data_; @@ -112,9 +108,7 @@ namespace cppast } private: - cpp_unexposed_type(std::string name) : name_(std::move(name)) - { - } + cpp_unexposed_type(std::string name) : name_(std::move(name)) {} cpp_type_kind do_get_kind() const noexcept override { @@ -180,9 +174,7 @@ namespace cppast } private: - cpp_builtin_type(cpp_builtin_type_kind kind) : kind_(kind) - { - } + cpp_builtin_type(cpp_builtin_type_kind kind) : kind_(kind) {} cpp_type_kind do_get_kind() const noexcept override { @@ -224,9 +216,7 @@ namespace cppast } private: - cpp_user_defined_type(cpp_type_ref entity) : entity_(std::move(entity)) - { - } + cpp_user_defined_type(cpp_type_ref entity) : entity_(std::move(entity)) {} cpp_type_kind do_get_kind() const noexcept override { @@ -361,6 +351,15 @@ namespace cppast cpp_cv cv_; }; + /// \returns The type without top-level const/volatile qualifiers. + const cpp_type& remove_cv(const cpp_type& type) noexcept; + + /// \returns The type without top-level const qualifiers. + const cpp_type& remove_const(const cpp_type& type) noexcept; + + /// \returns The type without top-level volatile qualifiers. + const cpp_type& remove_volatile(const cpp_type& type) noexcept; + /// A pointer to a [cppast::cpp_type](). class cpp_pointer_type final : public cpp_type { @@ -378,9 +377,7 @@ namespace cppast } private: - cpp_pointer_type(std::unique_ptr pointee) : pointee_(std::move(pointee)) - { - } + cpp_pointer_type(std::unique_ptr pointee) : pointee_(std::move(pointee)) {} cpp_type_kind do_get_kind() const noexcept override { diff --git a/src/cpp_type.cpp b/src/cpp_type.cpp index 10636f5..a624898 100644 --- a/src/cpp_type.cpp +++ b/src/cpp_type.cpp @@ -76,6 +76,39 @@ const char* cppast::to_string(cpp_builtin_type_kind kind) noexcept return "__ups"; } +const cpp_type& cppast::remove_cv(const cpp_type& type) noexcept +{ + if (type.kind() == cpp_type_kind::cv_qualified_t) + { + auto& cv = static_cast(type); + return cv.type(); + } + + return type; +} + +const cpp_type& cppast::remove_const(const cpp_type& type) noexcept +{ + if (type.kind() == cpp_type_kind::cv_qualified_t) + { + auto& cv = static_cast(type); + if (is_const(cv.cv_qualifier())) + return cv.type(); + } + return type; +} + +const cpp_type& cppast::remove_volatile(const cpp_type& type) noexcept +{ + if (type.kind() == cpp_type_kind::cv_qualified_t) + { + auto& cv = static_cast(type); + if (is_volatile(cv.cv_qualifier())) + return cv.type(); + } + return type; +} + bool detail::cpp_type_ref_predicate::operator()(const cpp_entity& e) { switch (e.kind()) @@ -559,13 +592,9 @@ std::string detail::to_string(const cpp_type& type) } private: - void do_indent() override - { - } + void do_indent() override {} - void do_unindent() override - { - } + void do_unindent() override {} void do_write_token_seq(string_view tokens) override { From 0531a19d90b511316ddd9635b45c1f131eb60847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Thu, 2 Nov 2017 18:46:00 +0100 Subject: [PATCH 08/13] Add comparison --- example/CMakeLists.txt | 1 + example/comparison.cpp | 117 +++++++++++++++++++++++++++++++++++++ example/enum_to_string.cpp | 3 +- example/serialization.cpp | 7 ++- include/cppast/visitor.hpp | 1 + 5 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 example/comparison.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 49b01a2..2f83c21 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -8,6 +8,7 @@ function(_cppast_example name) endfunction() _cppast_example(ast_printer) +_cppast_example(comparison) _cppast_example(documentation_generator) _cppast_example(enum_to_string) _cppast_example(serialization) diff --git a/example/comparison.cpp b/example/comparison.cpp new file mode 100644 index 0000000..f2a0468 --- /dev/null +++ b/example/comparison.cpp @@ -0,0 +1,117 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +/// \file +/// Generate equality comparisons. + +#include +#include + +#include +#include +#include + +#include "example_parser.hpp" + +bool has_token(const cppast::cpp_token_string& str, const char* token) +{ + auto iter = std::find_if(str.begin(), str.end(), + [&](const cppast::cpp_token& tok) { return tok.spelling == token; }); + return iter != str.end(); +} + +void generate_op_equal(std::ostream& out, const cppast::cpp_class& c) +{ + out << "inline bool operator==(const " << c.name() << "& lhs, const " << c.name() + << "& rhs) {\n"; + out << " return "; + + auto first = true; + + for (auto& base : c.bases()) + { + if (cppast::has_attribute(base, "generate::transient")) + continue; + + if (first) + first = false; + else + out << " && "; + out << "static_cast(lhs) == static_cast(rhs)\n"; + } + + for (auto& member : c) + if (member.kind() == cppast::cpp_entity_kind::member_variable_t + && !cppast::has_attribute(member, "generate::transient")) + { + // generate comparison code for non-transient member variables + if (first) + first = false; + else + out << " && "; + out << "lhs." << member.name() << " == " + << "rhs." << member.name() << "\n"; + } + + out << " ;\n"; + out << "}\n\n"; +} + +void generate_op_non_equal(std::ostream& out, const cppast::cpp_class& c) +{ + out << "inline bool operator!=(const " << c.name() << "& lhs, const " << c.name() + << "& rhs) {\n"; + out << " return !(lhs == rhs);\n"; + out << "}\n\n"; +} + +void generate_comparison(const cppast::cpp_file& file) +{ + cppast::visit(file, + [](const cppast::cpp_entity& e) { + // only visit non-templated class definitions that have the attribute set + return (!cppast::is_templated(e) + && e.kind() == cppast::cpp_entity_kind::class_t + && cppast::is_definition(e) + && cppast::has_attribute(e, "generate::comparison")) + // or all namespaces + || e.kind() == cppast::cpp_entity_kind::namespace_t; + }, + [](const cppast::cpp_entity& e, const cppast::visitor_info& info) { + if (e.kind() == cppast::cpp_entity_kind::class_t && !info.is_old_entity()) + { + auto& class_ = static_cast(e); + auto& attribute = + cppast::has_attribute(e, "generate::comparison").value(); + + if (attribute.arguments()) + { + if (has_token(attribute.arguments().value(), "==")) + generate_op_equal(std::cout, class_); + else if (has_token(attribute.arguments().value(), "!=")) + generate_op_non_equal(std::cout, class_); + } + else + { + generate_op_equal(std::cout, class_); + generate_op_non_equal(std::cout, class_); + } + } + else if (e.kind() == cppast::cpp_entity_kind::namespace_t) + { + if (info.event == cppast::visitor_info::container_entity_enter) + // open namespace + std::cout << "namespace " << e.name() << " {\n\n"; + else // if (info.event == cppast::visitor_info::container_entity_exit) + // close namespace + std::cout << "}\n"; + } + }); +} + +int main(int argc, char* argv[]) +{ + return example_main(argc, argv, {}, &generate_comparison); +} diff --git a/example/enum_to_string.cpp b/example/enum_to_string.cpp index 77065ff..02e0d0b 100644 --- a/example/enum_to_string.cpp +++ b/example/enum_to_string.cpp @@ -30,7 +30,8 @@ void generate_to_string(const cppast::cpp_file& file) auto& enum_ = static_cast(e); // write function header - std::cout << "const char* to_string(const " << enum_.name() << "& e) {\n"; + std::cout << "inline const char* to_string(const " << enum_.name() + << "& e) {\n"; // generate switch std::cout << " switch (e) {\n"; diff --git a/example/serialization.cpp b/example/serialization.cpp index af07bcf..ec7d7f3 100644 --- a/example/serialization.cpp +++ b/example/serialization.cpp @@ -77,13 +77,14 @@ void generate_serialize(const cppast::cpp_file& file) { auto& class_ = static_cast(e); - std::cout << "void serialize(const foo::serializer& s, const " + std::cout << "inline void serialize(const foo::serializer& s, const " << class_.name() << "& obj) {\n"; // serialize base classes for (auto& base : class_.bases()) - std::cout << " serialize(s, static_cast(obj));\n"; + if (!cppast::has_attribute(base, "generate::transient")) + std::cout << " serialize(s, static_cast(obj));\n"; // serialize member variables for (auto& member : class_) diff --git a/include/cppast/visitor.hpp b/include/cppast/visitor.hpp index 9f659ce..72f4f11 100644 --- a/include/cppast/visitor.hpp +++ b/include/cppast/visitor.hpp @@ -182,6 +182,7 @@ namespace cppast else return continue_visit; } + return continue_visit; }); } From e8f7dd0911ea1367ccf040b298ab44a3ad60a9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Wed, 8 Nov 2017 15:41:35 +0100 Subject: [PATCH 09/13] Fix bug in comparison code --- example/comparison.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/comparison.cpp b/example/comparison.cpp index f2a0468..1138571 100644 --- a/example/comparison.cpp +++ b/example/comparison.cpp @@ -90,7 +90,7 @@ void generate_comparison(const cppast::cpp_file& file) { if (has_token(attribute.arguments().value(), "==")) generate_op_equal(std::cout, class_); - else if (has_token(attribute.arguments().value(), "!=")) + if (has_token(attribute.arguments().value(), "!=")) generate_op_non_equal(std::cout, class_); } else From 0f9b90a32bd9c19af86acd41c4c0bd3fb467984e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Fri, 10 Nov 2017 09:33:37 +0100 Subject: [PATCH 10/13] Add name override for enum to string --- example/enum_to_string.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/example/enum_to_string.cpp b/example/enum_to_string.cpp index 02e0d0b..04bab5d 100644 --- a/example/enum_to_string.cpp +++ b/example/enum_to_string.cpp @@ -39,7 +39,14 @@ void generate_to_string(const cppast::cpp_file& file) { std::cout << " case " << enum_.name() << "::" << enumerator.name() << ":\n"; - std::cout << " return \"" << enumerator.name() << "\";\n"; + + if (auto attr = + cppast::has_attribute(enumerator, "generate::to_string")) + std::cout << " return " + << attr.value().arguments().value().as_string() + << ";\n"; + else + std::cout << " return \"" << enumerator.name() << "\";\n"; } std::cout << " }\n"; From 0b71d7d3b4d60f35f5d8db344f51828a21efec7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Fri, 10 Nov 2017 11:54:21 +0100 Subject: [PATCH 11/13] Add enum category example --- example/CMakeLists.txt | 1 + example/enum_category.cpp | 116 ++++++++++++++++++++++++++++++++++++ include/cppast/cpp_type.hpp | 6 +- src/cpp_function.cpp | 2 +- src/cpp_type.cpp | 2 +- 5 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 example/enum_category.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 2f83c21..c13a6c0 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -10,5 +10,6 @@ endfunction() _cppast_example(ast_printer) _cppast_example(comparison) _cppast_example(documentation_generator) +_cppast_example(enum_category) _cppast_example(enum_to_string) _cppast_example(serialization) diff --git a/example/enum_category.cpp b/example/enum_category.cpp new file mode 100644 index 0000000..8911b62 --- /dev/null +++ b/example/enum_category.cpp @@ -0,0 +1,116 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +/// \file +/// Generates enum category functions. + +#include +#include +#include + +#include // cpp_enum +#include // cpp_function +#include // visit() + +#include "example_parser.hpp" + +bool is_category(const cppast::cpp_enum_value& e, const std::string& name) +{ + if (auto attr = cppast::has_attribute(e, "generate::enum_category")) + { + auto iter = + std::find_if(attr.value().arguments().value().begin(), + attr.value().arguments().value().end(), + [&](const cppast::cpp_token& tok) { return tok.spelling == name; }); + return iter != attr.value().arguments().value().end(); + } + else + return false; +} + +const cppast::cpp_enum& get_enum(const cppast::cpp_entity_index& index, + const cppast::cpp_function_parameter& param) +{ + auto& param_type = param.type(); + // is an enum + assert(param_type.kind() == cppast::cpp_type_kind::user_defined_t); + auto& definition = + static_cast(param_type).entity().get(index)[0u].get(); + + assert(definition.kind() == cppast::cpp_entity_kind::enum_t); + return static_cast(definition); +} + +void generate_enum_category(const cppast::cpp_entity_index& index, const cppast::cpp_file& file) +{ + cppast::visit(file, + [](const cppast::cpp_entity& e) { + // only visit function declarations that have the attribute set + return (e.kind() == cppast::cpp_entity_kind::function_t + && !cppast::is_definition(e) + && cppast::has_attribute(e, "generate::enum_category")) + // or all namespaces + || e.kind() == cppast::cpp_entity_kind::namespace_t; + }, + [&](const cppast::cpp_entity& e, const cppast::visitor_info& info) { + if (e.kind() == cppast::cpp_entity_kind::function_t) + { + // a new function, generate implementation + assert(info.is_new_entity()); + + auto category = cppast::has_attribute(e, "generate::enum_category") + .value() + .arguments() + .value() + .as_string(); + + auto& func = static_cast(e); + // return type must be bool + assert(func.return_type().kind() == cppast::cpp_type_kind::builtin_t + && static_cast(func.return_type()) + .builtin_type_kind() + == cppast::cpp_bool); + + // single parameter... + assert(std::next(func.parameters().begin()) == func.parameters().end()); + auto& param = *func.parameters().begin(); + auto& enum_ = get_enum(index, param); + + // generate function definition + std::cout << "inline bool " << func.name() << "(" + << cppast::to_string(param.type()) << " e) {\n"; + + // generate switch + std::cout << " switch (e) {\n"; + for (const auto& enumerator : enum_) + { + std::cout << " case " << enum_.name() << "::" << enumerator.name() + << ":\n"; + if (is_category(enumerator, category)) + std::cout << " return true;\n"; + else + std::cout << " return false;\n"; + } + std::cout << " }\n"; + + std::cout << "}\n\n"; + } + else if (e.kind() == cppast::cpp_entity_kind::namespace_t) + { + if (info.event == cppast::visitor_info::container_entity_enter) + // open namespace + std::cout << "namespace " << e.name() << " {\n\n"; + else // if (info.event == cppast::visitor_info::container_entity_exit) + // close namespace + std::cout << "}\n"; + } + }); +} + +int main(int argc, char* argv[]) +{ + cppast::cpp_entity_index index; + return example_main(argc, argv, index, + [&](const cppast::cpp_file& file) { generate_enum_category(index, file); }); +} diff --git a/include/cppast/cpp_type.hpp b/include/cppast/cpp_type.hpp index 9bf3e02..19c707d 100644 --- a/include/cppast/cpp_type.hpp +++ b/include/cppast/cpp_type.hpp @@ -436,6 +436,9 @@ namespace cppast cpp_reference ref_; }; + /// \returns The type as a string representation. + std::string to_string(const cpp_type& type); + /// \exclude namespace detail { @@ -453,9 +456,6 @@ namespace cppast // write prefix, variadic, name, suffix void write_type(code_generator::output& output, const cpp_type& type, std::string name, bool is_variadic = false); - - // simple to_string() for types - std::string to_string(const cpp_type& type); } // namespace detail } // namespace cppast diff --git a/src/cpp_function.cpp b/src/cpp_function.cpp index f3f492b..a6d24e6 100644 --- a/src/cpp_function.cpp +++ b/src/cpp_function.cpp @@ -39,7 +39,7 @@ std::string cpp_function_base::do_get_signature() const { std::string result = "("; for (auto& param : parameters()) - result += detail::to_string(param.type()) + ','; + result += to_string(param.type()) + ','; if (is_variadic()) result += "..."; diff --git a/src/cpp_type.cpp b/src/cpp_type.cpp index a624898..c2dc88a 100644 --- a/src/cpp_type.cpp +++ b/src/cpp_type.cpp @@ -581,7 +581,7 @@ void detail::write_type(code_generator::output& output, const cpp_type& type, st write_type_suffix(output, type); } -std::string detail::to_string(const cpp_type& type) +std::string cppast::to_string(const cpp_type& type) { class to_string_generator : public code_generator { From ced136a3a4ab28821d6b4d2e9f5a5ba75456ca3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Fri, 10 Nov 2017 13:07:22 +0100 Subject: [PATCH 12/13] Fix comma in attributes on enum value --- src/libclang/cxtokenizer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libclang/cxtokenizer.cpp b/src/libclang/cxtokenizer.cpp index c2d7cc3..5ee8035 100644 --- a/src/libclang/cxtokenizer.cpp +++ b/src/libclang/cxtokenizer.cpp @@ -266,12 +266,12 @@ namespace while (!token_after_is(tu, file, cur, end, ";")) end = get_next_location(tu, file, end); } - else if (kind == CXCursor_EnumConstantDecl && !token_after_is(tu, file, cur, end, ",") - && !token_after_is(tu, file, cur, end, ";")) + else if (kind == CXCursor_EnumConstantDecl && !token_after_is(tu, file, cur, end, ",")) { - while (!token_after_is(tu, file, cur, end, ",") - && !token_after_is(tu, file, cur, end, ";")) - end = get_next_location(tu, file, end); + // need to support attributes + // just give up and extend the range to the range of the entire enum... + auto parent = clang_getCursorLexicalParent(cur); + end = clang_getRangeEnd(clang_getCursorExtent(parent)); } else if (kind == CXCursor_FieldDecl || kind == CXCursor_ParmDecl || kind == CXCursor_NonTypeTemplateParameter From 8532de14793e58b00c4564621b74b09d1af9d4c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Sat, 11 Nov 2017 10:33:01 +0100 Subject: [PATCH 13/13] Fix CMake --- example/CMakeLists.txt | 1 + src/CMakeLists.txt | 1 - test/CMakeLists.txt | 1 + tool/CMakeLists.txt | 2 +- 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index c13a6c0..784fd87 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -5,6 +5,7 @@ function(_cppast_example name) add_executable(cppast_example_${name} ${name}.cpp example_parser.hpp) target_link_libraries(cppast_example_${name} PUBLIC cppast) + set_target_properties(cppast_example_${name} PROPERTIES CXX_STANDARD 11) endfunction() _cppast_example(ast_printer) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 26cded2..34aa886 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -116,4 +116,3 @@ endif() if(CPPAST_ENABLE_PRECONDITION_CHECKS) target_compile_definitions(cppast PUBLIC CPPAST_ENABLE_PRECONDITION_CHECKS) endif() -set_target_properties(cppast PROPERTIES CXX_STANDARD 11) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6c75862..281a4b1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,6 +36,7 @@ set(tests add_executable(cppast_test test.cpp test_parser.hpp ${tests}) target_include_directories(cppast_test PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(cppast_test PUBLIC cppast) +set_target_properties(cppast_test PROPERTIES CXX_STANDARD 11) enable_testing() add_test(NAME test COMMAND cppast_test) diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt index 75661da..63e32a9 100644 --- a/tool/CMakeLists.txt +++ b/tool/CMakeLists.txt @@ -4,4 +4,4 @@ add_executable(cppast_tool main.cpp) target_link_libraries(cppast_tool PUBLIC cppast cxxopts) -set_target_properties(cppast_tool PROPERTIES OUTPUT_NAME cppast) +set_target_properties(cppast_tool PROPERTIES CXX_STANDARD 11 OUTPUT_NAME cppast)