Parse cpp_class

This commit is contained in:
Jonathan Müller 2017-02-23 21:26:45 +01:00
commit e0b76a07d0
9 changed files with 425 additions and 50 deletions

View file

@ -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<cpp_class, cpp_entity>
{
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)

View file

@ -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();
}

View file

@ -0,0 +1,109 @@
// Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include <cppast/cpp_class.hpp>
#include <clang-c/Index.h>
#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);
// [<attribute>] [virtual] [<access>] <name>
// 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<cpp_entity> 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));
}

View file

@ -37,9 +37,12 @@ std::unique_ptr<cpp_entity> 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;

View file

@ -53,9 +53,10 @@ namespace cppast
std::unique_ptr<cpp_entity> parse_cpp_type_alias(const parse_context& context,
const CXCursor& cur);
std::unique_ptr<cpp_entity> parse_cpp_enum(const parse_context& context,
const CXCursor& cur);
std::unique_ptr<cpp_entity> parse_cpp_class(const parse_context& context,
const CXCursor& cur);
std::unique_ptr<cpp_entity> parse_entity(const parse_context& context, const CXCursor& cur);
}

View file

@ -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];

View file

@ -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
// [[<attribute>]]
// ^
skip_brackets(stream);
// [[<attribute>]]
// ^
skip(stream, "]");
return true;
}
else if (skip_if(stream, "__attribute__"))
{
// GCC/clang attributes
// __attribute__(<attribute>)
// ^
skip_brackets(stream);
return true;
}
else if (skip_if(stream, "__declspec"))
{
// MSVC declspec
// __declspec(<attribute>)
// ^
skip_brackets(stream);
return true;
}
return false;
}
}
bool detail::skip_attribute(detail::token_stream& stream)
{
if (skip_if(stream, "[") && stream.peek() == "[")
{
// C++11 attribute
// [[<attribute>]]
// ^
skip_brackets(stream);
// [[<attribute>]]
// ^
skip(stream, "]");
return true;
}
else if (skip_if(stream, "__attribute__"))
{
// GCC/clang attributes
// __attribute__(<attribute>)
// ^
skip_brackets(stream);
return true;
}
else if (skip_if(stream, "__declspec"))
{
// MSVC declspec
// __declspec(<attribute>)
// ^
skip_brackets(stream);
return true;
}
return false;
auto any = false;
while (skip_attribute_impl(stream))
any = true;
return any;
}

230
test/cpp_class.cpp Normal file
View file

@ -0,0 +1,230 @@
// Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include <cppast/cpp_class.hpp>
#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<cpp_class>(*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<const cpp_access_specifier&>(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<const cpp_access_specifier&>(child).access_specifier()
== cpp_private);
break;
case 4:
REQUIRE(child.name() == "private");
REQUIRE(child.kind() == cpp_entity_kind::access_specifier_t);
REQUIRE(static_cast<const cpp_access_specifier&>(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<const cpp_access_specifier&>(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);
}

View file

@ -28,10 +28,8 @@ bool equal_types(const cpp_entity_index& idx, const cpp_type& parsed, const cpp_
auto user_synthesized = static_cast<const cpp_user_defined_type&>(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: