diff --git a/include/cppast/cpp_class.hpp b/include/cppast/cpp_class.hpp index 9762b3d..68964ef 100644 --- a/include/cppast/cpp_class.hpp +++ b/include/cppast/cpp_class.hpp @@ -37,6 +37,8 @@ namespace cppast class cpp_access_specifier final : public cpp_entity { public: + static cpp_entity_kind kind() noexcept; + /// \returns A newly created access specifier. /// \notes It is not meant to be registered at the [cppast::cpp_entity_index](), /// as nothing can refer to it. @@ -66,6 +68,8 @@ namespace cppast class cpp_base_class final : public cpp_entity { public: + static cpp_entity_kind kind() noexcept; + /// \returns A newly created base class specifier. /// \notes It is not meant to be registered at the [cppast::cpp_entity_index](), /// as nothing can refer to the specifier itself. @@ -111,16 +115,24 @@ namespace cppast class cpp_class final : public cpp_entity, public cpp_entity_container { public: + static cpp_entity_kind kind() noexcept; + /// Builds a [cppast::cpp_class](). class builder { public: /// \effects Sets the name and kind and whether it is `final`. - explicit builder(std::string name, cpp_class_kind kind, bool is_final) + explicit builder(std::string name, cpp_class_kind kind, bool is_final = false) : class_(new cpp_class(std::move(name), kind, is_final)) { } + /// \effects Marks the class as final. + void is_final() noexcept + { + class_->final_ = true; + } + /// \effects Builds a [cppast::cpp_base_class]() and adds it. void base_class(const cpp_type_ref& base, cpp_access_specifier_kind access, bool is_virtual) diff --git a/src/cpp_class.cpp b/src/cpp_class.cpp index 3687b8f..4b204d8 100644 --- a/src/cpp_class.cpp +++ b/src/cpp_class.cpp @@ -46,17 +46,32 @@ const char* cppast::to_string(cpp_access_specifier_kind access) noexcept return "should not get here either"; } -cpp_entity_kind cpp_access_specifier::do_get_entity_kind() const noexcept +cpp_entity_kind cpp_access_specifier::kind() noexcept { return cpp_entity_kind::access_specifier_t; } -cpp_entity_kind cpp_base_class::do_get_entity_kind() const noexcept +cpp_entity_kind cpp_access_specifier::do_get_entity_kind() const noexcept +{ + return kind(); +} + +cpp_entity_kind cpp_base_class::kind() noexcept { return cpp_entity_kind::base_class_t; } -cpp_entity_kind cpp_class::do_get_entity_kind() const noexcept +cpp_entity_kind cpp_base_class::do_get_entity_kind() const noexcept +{ + return kind(); +} + +cpp_entity_kind cpp_class::kind() noexcept { return cpp_entity_kind::class_t; } + +cpp_entity_kind cpp_class::do_get_entity_kind() const noexcept +{ + return kind(); +} diff --git a/src/libclang/class_parser.cpp b/src/libclang/class_parser.cpp new file mode 100644 index 0000000..3bd13f7 --- /dev/null +++ b/src/libclang/class_parser.cpp @@ -0,0 +1,109 @@ +// 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. + +#include +#include + +#include "libclang_visitor.hpp" +#include "parse_functions.hpp" + +using namespace cppast; + +namespace +{ + cpp_class_kind parse_class_kind(const CXCursor& cur) + { + switch (clang_getCursorKind(cur)) + { + case CXCursor_ClassDecl: + return cpp_class_kind::class_t; + case CXCursor_StructDecl: + return cpp_class_kind::struct_t; + case CXCursor_UnionDecl: + return cpp_class_kind::union_t; + default: + break; + } + DEBUG_UNREACHABLE(detail::assert_handler{}); + return cpp_class_kind::class_t; + } + + cpp_class::builder make_class_builder(const CXCursor& cur) + { + auto kind = parse_class_kind(cur); + auto name = detail::cxstring(clang_getCursorSpelling(cur)); + return cpp_class::builder(name.c_str(), kind); + } + + cpp_access_specifier_kind convert_access(const CXCursor& cur) + { + switch (clang_getCXXAccessSpecifier(cur)) + { + case CX_CXXInvalidAccessSpecifier: + break; + + case CX_CXXPublic: + return cpp_public; + case CX_CXXProtected: + return cpp_protected; + case CX_CXXPrivate: + return cpp_private; + } + + DEBUG_UNREACHABLE(detail::assert_handler{}); + return cpp_public; + } + + void add_access_specifier(cpp_class::builder& builder, const CXCursor& cur) + { + DEBUG_ASSERT(cur.kind == CXCursor_CXXAccessSpecifier, detail::assert_handler{}); + builder.access_specifier(convert_access(cur)); + } + + void add_base_class(cpp_class::builder& builder, const detail::parse_context& context, + const CXCursor& cur) + { + DEBUG_ASSERT(cur.kind == CXCursor_CXXBaseSpecifier, detail::assert_handler{}); + auto access = convert_access(cur); + auto is_virtual = clang_isVirtualBase(cur) != 0u; + + detail::tokenizer tokenizer(context.tu, context.file, cur); + detail::token_stream stream(tokenizer, cur); + + // [] [virtual] [] + // can't use spelling to get the name + detail::skip_attribute(stream); + if (is_virtual) + detail::skip(stream, "virtual"); + detail::skip_if(stream, to_string(access)); + + std::string name; + while (!stream.done()) + name += stream.get().c_str(); + + auto type = + cpp_type_ref(detail::get_entity_id(clang_getCursorReferenced(cur)), std::move(name)); + builder.base_class(type, access, is_virtual); + } +} + +std::unique_ptr detail::parse_cpp_class(const detail::parse_context& context, + const CXCursor& cur) +{ + if (!clang_isCursorDefinition(cur)) + return nullptr; + auto builder = make_class_builder(cur); + detail::visit_children(cur, [&](const CXCursor& child) { + auto kind = clang_getCursorKind(child); + if (kind == CXCursor_CXXAccessSpecifier) + add_access_specifier(builder, child); + else if (kind == CXCursor_CXXBaseSpecifier) + add_base_class(builder, context, child); + else if (kind == CXCursor_CXXFinalAttr) + builder.is_final(); + else if (auto entity = parse_entity(context, child)) + builder.add_child(std::move(entity)); + }); + return builder.finish(*context.idx, get_entity_id(cur)); +} diff --git a/src/libclang/parse_functions.cpp b/src/libclang/parse_functions.cpp index 993a0aa..4b6080e 100644 --- a/src/libclang/parse_functions.cpp +++ b/src/libclang/parse_functions.cpp @@ -37,9 +37,12 @@ std::unique_ptr detail::parse_entity(const detail::parse_context& co case CXCursor_TypeAliasDecl: case CXCursor_TypedefDecl: return parse_cpp_type_alias(context, cur); - case CXCursor_EnumDecl: return parse_cpp_enum(context, cur); + case CXCursor_ClassDecl: + case CXCursor_StructDecl: + case CXCursor_UnionDecl: + return parse_cpp_class(context, cur); default: break; diff --git a/src/libclang/parse_functions.hpp b/src/libclang/parse_functions.hpp index 055bd81..080772f 100644 --- a/src/libclang/parse_functions.hpp +++ b/src/libclang/parse_functions.hpp @@ -53,9 +53,10 @@ namespace cppast std::unique_ptr parse_cpp_type_alias(const parse_context& context, const CXCursor& cur); - std::unique_ptr parse_cpp_enum(const parse_context& context, const CXCursor& cur); + std::unique_ptr parse_cpp_class(const parse_context& context, + const CXCursor& cur); std::unique_ptr parse_entity(const parse_context& context, const CXCursor& cur); } diff --git a/src/libclang/raii_wrapper.hpp b/src/libclang/raii_wrapper.hpp index b328d66..03e2304 100644 --- a/src/libclang/raii_wrapper.hpp +++ b/src/libclang/raii_wrapper.hpp @@ -119,6 +119,11 @@ namespace cppast return str_ ? str_.value().c_str : ""; } + std::string std_str() const noexcept + { + return c_str(); + } + char operator[](std::size_t i) const noexcept { return c_str()[i]; diff --git a/src/libclang/tokenizer.cpp b/src/libclang/tokenizer.cpp index 8d51d2c..f913f6d 100644 --- a/src/libclang/tokenizer.cpp +++ b/src/libclang/tokenizer.cpp @@ -142,17 +142,8 @@ namespace end = prev; } } - else if (clang_getCursorKind(cur) == CXCursor_TypeAliasDecl - && !token_after_is(tu, file, cur, end, ";")) - { - // type alias tokens don't include everything - do - { - end = get_next_location(tu, file, end); - } while (!token_after_is(tu, file, cur, end, ";")); - end = get_next_location(tu, file, end); - } - else if (clang_isExpression(clang_getCursorKind(cur))) + else if (clang_isExpression(clang_getCursorKind(cur)) + || clang_getCursorKind(cur) == CXCursor_CXXBaseSpecifier) // need to shrink range by one end = get_next_location(tu, file, end, -1); @@ -236,35 +227,46 @@ void detail::skip_brackets(detail::token_stream& stream) stream.set_cur(std::next(closing)); } +namespace +{ + bool skip_attribute_impl(detail::token_stream& stream) + { + if (skip_if(stream, "[") && stream.peek() == "[") + { + // C++11 attribute + // [[]] + // ^ + skip_brackets(stream); + // [[]] + // ^ + skip(stream, "]"); + return true; + } + else if (skip_if(stream, "__attribute__")) + { + // GCC/clang attributes + // __attribute__() + // ^ + skip_brackets(stream); + return true; + } + else if (skip_if(stream, "__declspec")) + { + // MSVC declspec + // __declspec() + // ^ + skip_brackets(stream); + return true; + } + + return false; + } +} + bool detail::skip_attribute(detail::token_stream& stream) { - if (skip_if(stream, "[") && stream.peek() == "[") - { - // C++11 attribute - // [[]] - // ^ - skip_brackets(stream); - // [[]] - // ^ - skip(stream, "]"); - return true; - } - else if (skip_if(stream, "__attribute__")) - { - // GCC/clang attributes - // __attribute__() - // ^ - skip_brackets(stream); - return true; - } - else if (skip_if(stream, "__declspec")) - { - // MSVC declspec - // __declspec() - // ^ - skip_brackets(stream); - return true; - } - - return false; + auto any = false; + while (skip_attribute_impl(stream)) + any = true; + return any; } diff --git a/test/cpp_class.cpp b/test/cpp_class.cpp new file mode 100644 index 0000000..53f8f3f --- /dev/null +++ b/test/cpp_class.cpp @@ -0,0 +1,230 @@ +// 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. + +#include + +#include "test_parser.hpp" + +using namespace cppast; + +TEST_CASE("cpp_class") +{ + auto code = R"( +// basic +struct a {}; +class b final {}; +union c {}; + +struct ignore_me; + +// members +struct d +{ + enum m1 {}; + +public: + enum m2 {}; + +private: +private: + enum m3 {}; + +protected: + enum m4 {}; +}; + +// bases +class e +: a, private d {}; + +namespace ns +{ + struct base {}; +} + +struct f +: public ns::base, virtual protected e +{}; + +using namespace ns; + +struct g +: base {}; +)"; + + cpp_entity_index idx; + auto file = parse(idx, "cpp_class.cpp", code); + auto count = test_visit(*file, [&](const cpp_class& c) { + if (c.name() == "a" || c.name() == "base") + { + REQUIRE(c.class_kind() == cpp_class_kind::struct_t); + REQUIRE(!c.is_final()); + REQUIRE(c.bases().begin() == c.bases().end()); + REQUIRE(c.begin() == c.end()); + } + else if (c.name() == "b") + { + REQUIRE(c.class_kind() == cpp_class_kind::class_t); + REQUIRE(c.is_final()); + REQUIRE(c.bases().begin() == c.bases().end()); + REQUIRE(c.begin() == c.end()); + } + else if (c.name() == "c") + { + REQUIRE(c.class_kind() == cpp_class_kind::union_t); + REQUIRE(!c.is_final()); + REQUIRE(c.bases().begin() == c.bases().end()); + REQUIRE(c.begin() == c.end()); + } + else if (c.name() == "d") + { + REQUIRE(c.class_kind() == cpp_class_kind::struct_t); + REQUIRE(!c.is_final()); + REQUIRE(c.bases().begin() == c.bases().end()); + + auto no_children = 0u; + for (auto& child : c) + { + switch (no_children++) + { + case 0: + REQUIRE(child.name() == "m1"); + break; + case 1: + REQUIRE(child.name() == "public"); + REQUIRE(child.kind() == cpp_entity_kind::access_specifier_t); + REQUIRE(static_cast(child).access_specifier() + == cpp_public); + break; + case 2: + REQUIRE(child.name() == "m2"); + break; + case 3: + REQUIRE(child.name() == "private"); + REQUIRE(child.kind() == cpp_entity_kind::access_specifier_t); + REQUIRE(static_cast(child).access_specifier() + == cpp_private); + break; + case 4: + REQUIRE(child.name() == "private"); + REQUIRE(child.kind() == cpp_entity_kind::access_specifier_t); + REQUIRE(static_cast(child).access_specifier() + == cpp_private); + break; + case 5: + REQUIRE(child.name() == "m3"); + break; + case 6: + REQUIRE(child.name() == "protected"); + REQUIRE(child.kind() == cpp_entity_kind::access_specifier_t); + REQUIRE(static_cast(child).access_specifier() + == cpp_protected); + break; + case 7: + REQUIRE(child.name() == "m4"); + break; + default: + REQUIRE(false); + break; + } + } + REQUIRE(no_children == 8u); + } + else if (c.name() == "e") + { + REQUIRE(c.class_kind() == cpp_class_kind::class_t); + REQUIRE(!c.is_final()); + REQUIRE(c.begin() == c.end()); + + auto no_bases = 0u; + for (auto& base : c.bases()) + { + ++no_bases; + if (base.name() == "a") + { + REQUIRE(base.access_specifier() == cpp_private); + REQUIRE(!base.is_virtual()); + + auto entity = base.entity().get(idx); + REQUIRE(entity); + REQUIRE(entity.value().name() == "a"); + } + else if (base.name() == "d") + { + REQUIRE(base.access_specifier() == cpp_private); + REQUIRE(!base.is_virtual()); + + auto entity = base.entity().get(idx); + REQUIRE(entity); + REQUIRE(entity.value().name() == "d"); + } + else + REQUIRE(false); + } + REQUIRE(no_bases == 2u); + } + else if (c.name() == "f") + { + REQUIRE(c.class_kind() == cpp_class_kind::struct_t); + REQUIRE(!c.is_final()); + REQUIRE(c.begin() == c.end()); + + auto no_bases = 0u; + for (auto& base : c.bases()) + { + ++no_bases; + if (base.name() == "ns::base") + { + REQUIRE(base.access_specifier() == cpp_public); + REQUIRE(!base.is_virtual()); + + auto entity = base.entity().get(idx); + REQUIRE(entity); + REQUIRE(entity.value().name() == "base"); + REQUIRE(full_name(entity.value()) == "ns::base"); + } + else if (base.name() == "e") + { + REQUIRE(base.access_specifier() == cpp_protected); + REQUIRE(base.is_virtual()); + + auto entity = base.entity().get(idx); + REQUIRE(entity); + REQUIRE(entity.value().name() == "e"); + } + else + REQUIRE(false); + } + REQUIRE(no_bases == 2u); + } + else if (c.name() == "g") + { + REQUIRE(c.class_kind() == cpp_class_kind::struct_t); + REQUIRE(!c.is_final()); + REQUIRE(c.begin() == c.end()); + + auto no_bases = 0u; + for (auto& base : c.bases()) + { + ++no_bases; + if (base.name() == "base") + { + REQUIRE(base.access_specifier() == cpp_public); + REQUIRE(!base.is_virtual()); + + auto entity = base.entity().get(idx); + REQUIRE(entity); + REQUIRE(entity.value().name() == "base"); + REQUIRE(full_name(entity.value()) == "ns::base"); + } + else + REQUIRE(false); + } + REQUIRE(no_bases == 1u); + } + else + REQUIRE(false); + }); + REQUIRE(count == 8u); +} diff --git a/test/cpp_type_alias.cpp b/test/cpp_type_alias.cpp index 17f6b55..22d9d53 100644 --- a/test/cpp_type_alias.cpp +++ b/test/cpp_type_alias.cpp @@ -28,10 +28,8 @@ bool equal_types(const cpp_entity_index& idx, const cpp_type& parsed, const cpp_ auto user_synthesized = static_cast(synthesized).entity(); if (user_parsed.name() != user_synthesized.name()) return false; - return true; - // TODO: check that the referring also works (need parsing of structs) - // auto entity = user_parsed.get(idx); - // return entity.has_value() && entity.value().name() == user_parsed.name(); + auto entity = user_parsed.get(idx); + return entity.has_value() && entity.value().name() == user_parsed.name(); } case cpp_type_kind::cv_qualified: