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..784fd87 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,16 @@ +# 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 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) +_cppast_example(comparison) +_cppast_example(documentation_generator) +_cppast_example(enum_category) +_cppast_example(enum_to_string) +_cppast_example(serialization) diff --git a/example/ast_printer.cpp b/example/ast_printer.cpp new file mode 100644 index 0000000..a06eb31 --- /dev/null +++ b/example/ast_printer.cpp @@ -0,0 +1,33 @@ +// 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 // 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'; + }); +} + +int main(int argc, char* argv[]) +{ + return example_main(argc, argv, {}, &print_ast); +} diff --git a/example/comparison.cpp b/example/comparison.cpp new file mode 100644 index 0000000..1138571 --- /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_); + 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/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); +} 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/example/enum_to_string.cpp b/example/enum_to_string.cpp new file mode 100644 index 0000000..04bab5d --- /dev/null +++ b/example/enum_to_string.cpp @@ -0,0 +1,70 @@ +// 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 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; + }, + [](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 << "inline 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"; + + 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"; + + 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); +} diff --git a/example/example_parser.hpp b/example/example_parser.hpp new file mode 100644 index 0000000..b91c12c --- /dev/null +++ b/example/example_parser.hpp @@ -0,0 +1,56 @@ +// 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) try +{ + 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; +} +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..ec7d7f3 --- /dev/null +++ b/example/serialization.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 +/// 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 << "inline void serialize(const foo::serializer& s, const " + << class_.name() << "& obj) {\n"; + + // serialize base classes + for (auto& base : class_.bases()) + if (!cppast::has_attribute(base, "generate::transient")) + 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..19c707d 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 { @@ -439,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 { @@ -456,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/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/include/cppast/visitor.hpp b/include/cppast/visitor.hpp index 889c194..72f4f11 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,212 @@ 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; + } + 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/src/CMakeLists.txt b/src/CMakeLists.txt index c45d7bb..34aa886 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 @@ -115,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/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 10636f5..c2dc88a 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()) @@ -548,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 { @@ -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 { 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 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) { 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); + } + } } diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt index 8f3aaf0..63e32a9 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 CXX_STANDARD 11 OUTPUT_NAME cppast)