Add support for attributes

This commit is contained in:
Jonathan Müller 2017-10-31 13:16:34 +01:00
commit d8a230bee7
18 changed files with 827 additions and 89 deletions

View file

@ -0,0 +1,123 @@
// 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.
#ifndef CPPAST_CPP_ATTRIBUTE_HPP_INCLUDED
#define CPPAST_CPP_ATTRIBUTE_HPP_INCLUDED
#include <string>
#include <vector>
#include <type_safe/optional.hpp>
#include <type_safe/optional_ref.hpp>
#include <cppast/cpp_token.hpp>
namespace cppast
{
/// The known C++ attributes.
enum class cpp_attribute_kind
{
// update get_attribute_kind() in tokenizer, when updating this
alignas_,
carries_dependency,
deprecated,
fallthrough,
maybe_unused,
nodiscard,
noreturn,
unknown, //< An unknown attribute.
};
/// A C++ attribute, including `alignas` specifiers.
///
/// It consists of a name, an optional namespace scope and optional arguments.
/// The scope is just a single identifier and doesn't include the `::` and can be given explicitly or via using.
/// The arguments are as specified in the source code but do not include the outer-most `(` and `)`.
/// It can also be variadic or not.
///
/// An attribute can be known or unknown.
/// A known attribute will have the [cppast::cpp_attribute_kind]() set properly.
class cpp_attribute
{
public:
/// \effects Creates a known attribute, potentially with arguments.
cpp_attribute(cpp_attribute_kind kind, type_safe::optional<cpp_token_string> arguments);
/// \effects Creates an unknown attribute giving it the optional scope, names, arguments and whether it is variadic.
cpp_attribute(type_safe::optional<std::string> scope, std::string name,
type_safe::optional<cpp_token_string> arguments, bool is_variadic)
: scope_(std::move(scope)),
arguments_(std::move(arguments)),
name_(std::move(name)),
variadic_(is_variadic)
{
}
/// \returns The kind of attribute, if it is known.
const cpp_attribute_kind& kind() const noexcept
{
return kind_;
}
/// \returns The name of the attribute.
const std::string& name() const noexcept
{
return name_;
}
/// \returns The scope of the attribute, if there is any.
const type_safe::optional<std::string>& scope() const noexcept
{
return scope_;
}
/// \returns Whether or not the attribute is variadic.
bool is_variadic() const noexcept
{
return variadic_;
}
/// \returns The arguments of the attribute, if they are any.
const type_safe::optional<cpp_token_string>& arguments() const noexcept
{
return arguments_;
}
private:
type_safe::optional<std::string> scope_;
type_safe::optional<cpp_token_string> arguments_;
std::string name_;
cpp_attribute_kind kind_ = cpp_attribute_kind::unknown;
bool variadic_;
};
/// A list of C++ attributes.
using cpp_attribute_list = std::vector<cpp_attribute>;
/// Checks whether an attribute is given.
/// \returns `true` if the given attribute list (1-2) / entity (3-4) contain
/// an attribute of the given name (1+3) / kind (2+4).
/// `false` otherwise.
/// \group has_attribute
type_safe::optional_ref<const cpp_attribute> has_attribute(const cpp_attribute_list& attributes,
const std::string& name);
/// \group has_attribute
type_safe::optional_ref<const cpp_attribute> has_attribute(const cpp_attribute_list& attributes,
cpp_attribute_kind kind);
class cpp_entity;
/// \group has_attribute
type_safe::optional_ref<const cpp_attribute> has_attribute(const cpp_entity& e,
const std::string& name);
/// \group has_attribute
type_safe::optional_ref<const cpp_attribute> has_attribute(const cpp_entity& e,
cpp_attribute_kind kind);
} // namespace cppast
#endif // CPPAST_CPP_ATTRIBUTE_HPP_INCLUDED

View file

@ -144,17 +144,19 @@ namespace cppast
}
/// \effects Builds a [cppast::cpp_base_class]() and adds it.
void base_class(std::string name, std::unique_ptr<cpp_type> type,
cpp_access_specifier_kind access, bool is_virtual)
cpp_base_class& base_class(std::string name, std::unique_ptr<cpp_type> type,
cpp_access_specifier_kind access, bool is_virtual)
{
add_base_class(
return add_base_class(
cpp_base_class::build(std::move(name), std::move(type), access, is_virtual));
}
/// \effects Adds a new base class.
void add_base_class(std::unique_ptr<cpp_base_class> base) noexcept
cpp_base_class& add_base_class(std::unique_ptr<cpp_base_class> base) noexcept
{
auto bptr = base.get();
class_->bases_.push_back(*class_, std::move(base));
return *bptr;
}
/// \effects Builds a [cppast::cpp_access_specifier]() and adds it.

View file

@ -11,6 +11,7 @@
#include <type_safe/optional_ref.hpp>
#include <cppast/detail/intrusive_list.hpp>
#include <cppast/cpp_attribute.hpp>
#include <cppast/cpp_token.hpp>
namespace cppast
@ -123,6 +124,24 @@ namespace cppast
comment_ = comment.value_or("");
}
/// \returns The list of attributes that are specified for that entity.
const cpp_attribute_list& attributes() const noexcept
{
return attributes_;
}
/// \effects Adds an attribute for that entity.
void add_attribute(cpp_attribute attr) noexcept
{
attributes_.push_back(std::move(attr));
}
/// \effects Adds multiple arguments for that entity.
void add_attribute(const cpp_attribute_list& list) noexcept
{
attributes_.insert(attributes_.end(), list.begin(), list.end());
}
/// \returns The specified user data.
void* user_data() const noexcept
{
@ -162,6 +181,7 @@ namespace cppast
std::string name_;
std::string comment_;
cpp_attribute_list attributes_;
type_safe::optional_ref<const cpp_entity> parent_;
mutable std::atomic<void*> user_data_;

View file

@ -10,6 +10,7 @@ set(header
../include/cppast/compile_config.hpp
../include/cppast/cpp_alias_template.hpp
../include/cppast/cpp_array_type.hpp
../include/cppast/cpp_attribute.hpp
../include/cppast/cpp_class.hpp
../include/cppast/cpp_class_template.hpp
../include/cppast/cpp_decltype_type.hpp
@ -50,6 +51,7 @@ set(header
set(source
code_generator.cpp
cpp_alias_template.cpp
cpp_attribute.cpp
cpp_class.cpp
cpp_class_template.cpp
cpp_entity.cpp
@ -78,6 +80,8 @@ set(source
visitor.cpp)
set(libclang_source
libclang/class_parser.cpp
libclang/cxtokenizer.cpp
libclang/cxtokenizer.hpp
libclang/debug_helper.cpp
libclang/debug_helper.hpp
libclang/enum_parser.cpp
@ -95,8 +99,6 @@ set(libclang_source
libclang/preprocessor.hpp
libclang/raii_wrapper.hpp
libclang/template_parser.cpp
libclang/cxtokenizer.cpp
libclang/cxtokenizer.hpp
libclang/type_parser.cpp
libclang/variable_parser.cpp)

89
src/cpp_attribute.cpp Normal file
View file

@ -0,0 +1,89 @@
// 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_attribute.hpp>
#include <algorithm>
#include <cppast/cpp_entity.hpp>
using namespace cppast;
namespace
{
const char* get_attribute_name(cpp_attribute_kind kind) noexcept
{
switch (kind)
{
case cpp_attribute_kind::alignas_:
return "alignas";
case cpp_attribute_kind::carries_dependency:
return "carries_dependency";
case cpp_attribute_kind::deprecated:
return "deprecated";
case cpp_attribute_kind::fallthrough:
return "fallthrough";
case cpp_attribute_kind::maybe_unused:
return "maybe_unused";
case cpp_attribute_kind::nodiscard:
return "nodiscard";
case cpp_attribute_kind::noreturn:
return "noreturn";
case cpp_attribute_kind::unknown:
return "unknown";
}
return "<error>";
}
}
cpp_attribute::cpp_attribute(cpp_attribute_kind kind,
type_safe::optional<cpp_token_string> arguments)
: cpp_attribute(type_safe::nullopt, get_attribute_name(kind), std::move(arguments), false)
{
kind_ = kind;
}
type_safe::optional_ref<const cpp_attribute> cppast::has_attribute(
const cpp_attribute_list& attributes, const std::string& name)
{
auto iter =
std::find_if(attributes.begin(), attributes.end(), [&](const cpp_attribute& attribute) {
if (attribute.scope())
return attribute.scope().value() + "::" + attribute.name() == name;
else
return attribute.name() == name;
});
if (iter == attributes.end())
return nullptr;
else
return type_safe::ref(*iter);
}
type_safe::optional_ref<const cpp_attribute> cppast::has_attribute(
const cpp_attribute_list& attributes, cpp_attribute_kind kind)
{
auto iter =
std::find_if(attributes.begin(), attributes.end(),
[&](const cpp_attribute& attribute) { return attribute.kind() == kind; });
if (iter == attributes.end())
return nullptr;
else
return type_safe::ref(*iter);
}
type_safe::optional_ref<const cpp_attribute> cppast::has_attribute(const cpp_entity& e,
const std::string& name)
{
return has_attribute(e.attributes(), name);
}
type_safe::optional_ref<const cpp_attribute> cppast::has_attribute(const cpp_entity& e,
cpp_attribute_kind kind)
{
return has_attribute(e.attributes(), kind);
}

View file

@ -12,18 +12,32 @@ using namespace cppast;
namespace
{
cpp_class_kind parse_class_kind(const CXCursor& cur)
cpp_class_kind parse_class_kind(detail::cxtoken_stream& stream)
{
auto kind = clang_getTemplateCursorKind(cur);
auto kind = clang_getTemplateCursorKind(stream.cursor());
if (kind == CXCursor_NoDeclFound)
kind = clang_getCursorKind(cur);
kind = clang_getCursorKind(stream.cursor());
if (detail::skip_if(stream, "template"))
// skip template parameters
detail::skip_brackets(stream);
detail::skip_if(stream, "friend");
if (detail::skip_if(stream, "extern"))
// extern template
detail::skip(stream, "template");
switch (kind)
{
case CXCursor_ClassDecl:
detail::skip(stream, "class");
return cpp_class_kind::class_t;
case CXCursor_StructDecl:
detail::skip(stream, "struct");
return cpp_class_kind::struct_t;
case CXCursor_UnionDecl:
detail::skip(stream, "union");
return cpp_class_kind::union_t;
default:
break;
@ -32,11 +46,18 @@ namespace
return cpp_class_kind::class_t;
}
cpp_class::builder make_class_builder(const CXCursor& cur)
cpp_class::builder make_class_builder(const detail::parse_context& context, const CXCursor& cur)
{
auto kind = parse_class_kind(cur);
auto name = detail::get_cursor_name(cur);
return cpp_class::builder(name.c_str(), kind);
detail::cxtokenizer tokenizer(context.tu, context.file, cur);
detail::cxtoken_stream stream(tokenizer, cur);
auto kind = parse_class_kind(stream);
auto attributes = detail::parse_attributes(stream);
auto name = detail::get_cursor_name(cur);
auto result = cpp_class::builder(name.c_str(), kind);
result.get().add_attribute(attributes);
return result;
}
cpp_access_specifier_kind convert_access(const CXCursor& cur)
@ -76,15 +97,16 @@ namespace
// [<attribute>] [virtual] [<access>] <name>
// can't use spelling to get the name
detail::skip_attribute(stream);
auto attributes = detail::parse_attributes(stream);
if (is_virtual)
detail::skip(stream, "virtual");
detail::skip_if(stream, to_string(access));
auto name = detail::to_string(stream, stream.end()).as_string();
auto type = detail::parse_type(context, class_cur, clang_getCursorType(cur));
builder.base_class(std::move(name), std::move(type), access, is_virtual);
auto type = detail::parse_type(context, class_cur, clang_getCursorType(cur));
auto& base = builder.base_class(std::move(name), std::move(type), access, is_virtual);
base.add_attribute(attributes);
}
}
@ -100,7 +122,7 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_class(const detail::parse_context&
auto is_friend = false;
#endif
auto builder = make_class_builder(cur);
auto builder = make_class_builder(context, cur);
type_safe::optional<cpp_entity_ref> semantic_parent;
if (!is_friend)
{

View file

@ -73,11 +73,16 @@ namespace
};
bool token_after_is(const CXTranslationUnit& tu, const CXFile& file, const CXCursor& cur,
const CXSourceLocation& loc, const char* token_str)
const CXSourceLocation& loc, const char* token_str, int inc = 1)
{
auto loc_after = get_next_location(tu, file, loc);
auto loc_after = get_next_location(tu, file, loc, inc);
if (!clang_Location_isFromMainFile(loc_after))
return false;
simple_tokenizer tokenizer(tu, clang_getRange(loc, loc_after), cur);
simple_tokenizer tokenizer(tu,
inc > 0 ? clang_getRange(loc, loc_after) :
clang_getRange(loc_after, loc),
cur);
detail::cxstring spelling(clang_getTokenSpelling(tu, tokenizer[0u]));
return spelling == token_str;
}
@ -96,10 +101,55 @@ namespace
auto end = clang_getRangeEnd(extent);
auto kind = clang_getCursorKind(cur);
if (cursor_is_function(kind) || cursor_is_function(clang_getTemplateCursorKind(cur))
|| kind == CXCursor_VarDecl || kind == CXCursor_FieldDecl || kind == CXCursor_ParmDecl
|| kind == CXCursor_NonTypeTemplateParameter)
{
if (token_after_is(tu, file, cur, begin, "]", -2)
&& token_after_is(tu, file, cur, begin, "]", -3))
{
while (!token_after_is(tu, file, cur, begin, "[", -1)
&& !token_after_is(tu, file, cur, begin, "[", -2))
begin = get_next_location(tu, file, begin, -1);
begin = get_next_location(tu, file, begin, -3);
DEBUG_ASSERT(token_after_is(tu, file, cur, begin, "[")
&& token_after_is(tu, file, cur,
get_next_location(tu, file, begin), "["),
detail::parse_error_handler{}, cur,
"error in pre-function attribute parsing");
}
else if (token_after_is(tu, file, cur, begin, ")", -2))
{
// maybe alignas specifier
auto save_begin = begin;
auto paren_count = 1;
begin = get_next_location(tu, file, begin, -1);
for (auto last_begin = begin; paren_count != 0; last_begin = begin)
{
begin = get_next_location(tu, file, begin, -1);
if (token_after_is(tu, file, cur, begin, "(", -1))
--paren_count;
else if (token_after_is(tu, file, cur, begin, ")", -1))
++paren_count;
DEBUG_ASSERT(!clang_equalLocations(last_begin, begin),
detail::parse_error_handler{}, cur,
"infinite loop in alignas parsing");
}
begin = get_next_location(tu, file, begin, -(int(std::strlen("alignas")) + 1));
if (token_after_is(tu, file, cur, begin, "alignas"))
begin = get_next_location(tu, file, begin, -1);
else
begin = save_begin;
}
}
if (cursor_is_function(kind) || cursor_is_function(clang_getTemplateCursorKind(cur)))
{
auto is_definition = false;
// if a function we need to remove the body
// it does not need to be parsed
detail::visit_children(cur, [&](const CXCursor& child) {
@ -216,6 +266,13 @@ 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, ";"))
{
while (!token_after_is(tu, file, cur, end, ",")
&& !token_after_is(tu, file, cur, end, ";"))
end = get_next_location(tu, file, end);
}
else if (kind == CXCursor_FieldDecl || kind == CXCursor_ParmDecl
|| kind == CXCursor_NonTypeTemplateParameter
|| kind == CXCursor_TemplateTemplateParameter
@ -368,19 +425,121 @@ void detail::skip_brackets(detail::cxtoken_stream& stream)
namespace
{
bool skip_attribute_impl(detail::cxtoken_stream& stream)
type_safe::optional<std::string> parse_attribute_using(detail::cxtoken_stream& stream)
{
// using identifier :
if (skip_if(stream, "using"))
{
DEBUG_ASSERT(stream.peek().kind() == CXToken_Identifier, detail::parse_error_handler{},
stream.cursor(), "expected identifier");
auto scope = stream.get().value().std_str();
skip(stream, ":");
return scope;
}
else
return type_safe::nullopt;
}
cpp_attribute_kind get_attribute_kind(const std::string& name)
{
if (name == "carries_dependency")
return cpp_attribute_kind::carries_dependency;
else if (name == "deprecated")
return cpp_attribute_kind::deprecated;
else if (name == "fallthrough")
return cpp_attribute_kind::fallthrough;
else if (name == "maybe_unused")
return cpp_attribute_kind::maybe_unused;
else if (name == "nodiscard")
return cpp_attribute_kind::nodiscard;
else if (name == "noreturn")
return cpp_attribute_kind::noreturn;
else
return cpp_attribute_kind::unknown;
}
cpp_token_string parse_attribute_arguments(detail::cxtoken_stream& stream)
{
auto end = find_closing_bracket(stream);
skip(stream, "(");
auto arguments = detail::to_string(stream, end);
stream.set_cur(end);
skip(stream, ")");
return arguments;
}
cpp_attribute parse_attribute_token(detail::cxtoken_stream& stream,
type_safe::optional<std::string> scope)
{
// (identifier ::)_opt identifier ( '(' some tokens ')' )_opt ..._opt
// parse name
DEBUG_ASSERT(stream.peek().kind() == CXToken_Identifier, detail::parse_error_handler{},
stream.cursor(), "expected identifier");
auto name = stream.get().value().std_str();
if (skip_if(stream, "::"))
{
// name was actually a scope, so parse name again
DEBUG_ASSERT(!scope, detail::parse_error_handler{}, stream.cursor(),
"attribute using + scope not allowed");
scope = std::move(name);
DEBUG_ASSERT(stream.peek().kind() == CXToken_Identifier, detail::parse_error_handler{},
stream.cursor(), "expected identifier");
name = stream.get().value().std_str();
}
// parse arguments
type_safe::optional<cpp_token_string> arguments;
if (stream.peek() == "(")
arguments = parse_attribute_arguments(stream);
// parse variadic token
auto is_variadic = skip_if(stream, "...");
// get kind
auto kind = get_attribute_kind(name);
if (!scope && kind != cpp_attribute_kind::unknown)
return cpp_attribute(kind, std::move(arguments));
else
return cpp_attribute(std::move(scope), std::move(name), std::move(arguments),
is_variadic);
}
bool parse_attribute_impl(cpp_attribute_list& result, detail::cxtoken_stream& stream)
{
if (skip_if(stream, "[") && stream.peek() == "[")
{
// C++11 attribute
// [[<attribute>]]
// ^
skip_brackets(stream);
skip(stream, "[");
auto scope = parse_attribute_using(stream);
while (!skip_if(stream, "]"))
{
auto attribute = parse_attribute_token(stream, scope);
result.push_back(std::move(attribute));
detail::skip_if(stream, ",");
}
// [[<attribute>]]
// ^
skip(stream, "]");
return true;
}
else if (skip_if(stream, "alignas"))
{
// alignas specifier
// alignas(<some arguments>)
// ^
auto arguments = parse_attribute_arguments(stream);
result.push_back(cpp_attribute(cpp_attribute_kind::alignas_, std::move(arguments)));
}
else if (skip_if(stream, "__attribute__"))
{
// GCC/clang attributes
@ -402,12 +561,17 @@ namespace
}
}
bool detail::skip_attribute(detail::cxtoken_stream& stream)
cpp_attribute_list detail::parse_attributes(detail::cxtoken_stream& stream, bool skip_anway)
{
auto any = false;
while (skip_attribute_impl(stream))
any = true;
return any;
cpp_attribute_list result;
while (parse_attribute_impl(result, stream))
skip_anway = false;
if (skip_anway)
stream.bump();
return result;
}
namespace

View file

@ -8,6 +8,7 @@
#include <string>
#include <vector>
#include <cppast/cpp_attribute.hpp>
#include <cppast/cpp_token.hpp>
#include "raii_wrapper.hpp"
@ -189,8 +190,9 @@ namespace cppast
// note: < might not work in the arguments of a template specialization
void skip_brackets(cxtoken_stream& stream);
// skips an attribute
bool skip_attribute(cxtoken_stream& stream);
// parses attributes
// if skip_anyway is true it will bump even if no attributes have been parsed
cpp_attribute_list parse_attributes(cxtoken_stream& stream, bool skip_anyway = false);
// converts a token range to a string
cpp_token_string to_string(cxtoken_stream& stream, cxtoken_iterator end);

View file

@ -25,8 +25,8 @@ namespace
// <identifier> [<attribute>],
// or: <identifier> [<attribute>] = <expression>,
auto& name = stream.get().value();
detail::skip_attribute(stream);
auto& name = stream.get().value();
auto attributes = detail::parse_attributes(stream);
std::unique_ptr<cpp_expression> value;
if (detail::skip_if(stream, "="))
@ -40,8 +40,10 @@ namespace
});
}
return cpp_enum_value::build(*context.idx, detail::get_entity_id(cur), name.c_str(),
std::move(value));
auto result = cpp_enum_value::build(*context.idx, detail::get_entity_id(cur), name.c_str(),
std::move(value));
result->add_attribute(attributes);
return result;
}
cpp_enum::builder make_enum_builder(const detail::parse_context& context, const CXCursor& cur,
@ -51,11 +53,11 @@ namespace
detail::cxtokenizer tokenizer(context.tu, context.file, cur);
detail::cxtoken_stream stream(tokenizer, cur);
// [<attribute>] enum [class] [<attribute>] name [: type] {
detail::skip_attribute(stream);
// enum [class/struct] [<attribute>] name [: type] {
detail::skip(stream, "enum");
auto scoped = detail::skip_if(stream, "class");
detail::skip_attribute(stream);
auto scoped = detail::skip_if(stream, "class") || detail::skip_if(stream, "struct");
auto attributes = detail::parse_attributes(stream);
std::string scope;
while (!detail::skip_if(stream, name.c_str()))
if (!detail::append_scope(stream, scope))
@ -70,7 +72,9 @@ namespace
auto type = detail::parse_type(context, cur, clang_getEnumDeclIntegerType(cur));
auto type_given = detail::skip_if(stream, ":");
return cpp_enum::builder(name.c_str(), scoped, std::move(type), type_given);
auto result = cpp_enum::builder(name.c_str(), scoped, std::move(type), type_given);
result.get().add_attribute(attributes);
return result;
}
}

View file

@ -18,14 +18,18 @@ namespace
auto name = detail::get_cursor_name(cur);
auto type = detail::parse_type(context, cur, clang_getCursorType(cur));
auto default_value = detail::parse_default_value(context, cur, name.c_str());
cpp_attribute_list attributes;
auto default_value = detail::parse_default_value(attributes, context, cur, name.c_str());
std::unique_ptr<cpp_function_parameter> result;
if (name.empty())
return cpp_function_parameter::build(std::move(type), std::move(default_value));
result = cpp_function_parameter::build(std::move(type), std::move(default_value));
else
return cpp_function_parameter::build(*context.idx, detail::get_entity_id(cur),
name.c_str(), std::move(type),
std::move(default_value));
result = cpp_function_parameter::build(*context.idx, detail::get_entity_id(cur),
name.c_str(), std::move(type),
std::move(default_value));
result->add_attribute(attributes);
return result;
}
template <class Builder>
@ -212,10 +216,11 @@ namespace
// just the tokens occurring in the prefix
struct prefix_info
{
bool is_constexpr = false;
bool is_virtual = false;
bool is_explicit = false;
bool is_friend = false;
cpp_attribute_list attributes;
bool is_constexpr = false;
bool is_virtual = false;
bool is_explicit = false;
bool is_friend = false;
};
bool prefix_end(detail::cxtoken_stream& stream, const char* name, bool is_ctor)
@ -275,7 +280,11 @@ namespace
else if (detail::skip_if(stream, "explicit"))
result.is_explicit = true;
else
stream.bump();
{
auto attributes = detail::parse_attributes(stream, true);
result.attributes.insert(result.attributes.end(), attributes.begin(),
attributes.end());
}
}
DEBUG_ASSERT(!stream.done(), detail::parse_error_handler{}, stream.cursor(),
"unable to find end of function prefix");
@ -283,12 +292,16 @@ namespace
{ // function name can be enclosed in parentheses
}
auto attributes = detail::parse_attributes(stream);
result.attributes.insert(result.attributes.end(), attributes.begin(), attributes.end());
return result;
}
// just the tokens occurring in the suffix
struct suffix_info
{
cpp_attribute_list attributes;
std::unique_ptr<cpp_expression> noexcept_condition;
cpp_function_body_kind body_kind;
cpp_cv cv_qualifier = cpp_cv_none;
@ -392,12 +405,13 @@ namespace
suffix_info result(stream.cursor());
// syntax: <attribute> <cv> <ref> <exception>
detail::skip_attribute(stream);
result.attributes = detail::parse_attributes(stream);
if (allow_qualifier)
{
result.cv_qualifier = parse_cv(stream);
result.ref_qualifier = parse_ref(stream);
}
if (detail::skip_if(stream, "throw"))
// just because I can
detail::skip_brackets(stream);
@ -414,14 +428,17 @@ namespace
// check for trailing return type
if (detail::skip_if(stream, "->"))
{
//detail::print_tokens(context.tu, context.file, stream.cursor());
// this is rather tricky to skip
// so loop over all tokens and see if matching keytokens occur
// note that this isn't quite correct
// use a heuristic to skip brackets, which should be good enough
while (!stream.done())
{
if (stream.peek() == "(" || stream.peek() == "[" || stream.peek() == "<")
auto attributes = detail::parse_attributes(stream);
if (!attributes.empty())
result.attributes.insert(result.attributes.end(), attributes.begin(),
attributes.end());
else if (stream.peek() == "(" || stream.peek() == "[" || stream.peek() == "<")
detail::skip_brackets(stream);
else if (stream.peek() == "{")
// begin of definition
@ -472,6 +489,11 @@ namespace
result.virtual_keywords.value() |= cpp_virtual_flags::override;
}
auto attributes = detail::parse_attributes(stream);
if (!attributes.empty())
result.attributes.insert(result.attributes.end(), attributes.begin(),
attributes.end());
if (detail::skip_if(stream, "="))
parse_body(stream, result, allow_virtual);
else if (detail::skip_if(stream, "{") || detail::skip_if(stream, ":")
@ -499,6 +521,7 @@ namespace
detail::parse_type(context, cur,
clang_getCursorResultType(cur)));
context.comments.match(builder.get(), cur);
builder.get().add_attribute(prefix.attributes);
add_parameters(context, builder, cur);
if (clang_Cursor_isVariadic(cur))
@ -512,6 +535,7 @@ namespace
skip_parameters(stream);
auto suffix = parse_suffix_info(stream, context, false, false);
builder.get().add_attribute(suffix.attributes);
if (suffix.noexcept_condition)
builder.noexcept_condition(std::move(suffix.noexcept_condition));
@ -617,6 +641,7 @@ namespace
auto allow_qualifiers = set_qualifier(0, builder, cpp_cv_none, cpp_ref_none);
auto suffix = parse_suffix_info(stream, context, allow_qualifiers, true);
builder.get().add_attribute(suffix.attributes);
set_qualifier(0, builder, suffix.cv_qualifier, suffix.ref_qualifier);
if (suffix.noexcept_condition)
builder.noexcept_condition(move(suffix.noexcept_condition));
@ -651,6 +676,7 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_member_function(const detail::pars
detail::parse_type(context, cur,
clang_getCursorResultType(cur)));
context.comments.match(builder.get(), cur);
builder.get().add_attribute(prefix.attributes);
add_parameters(context, builder, cur);
if (clang_Cursor_isVariadic(cur))
builder.is_variadic();
@ -674,6 +700,7 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_conversion_op(const detail::parse_
detail::cxtoken_stream stream(tokenizer, cur);
auto prefix = parse_prefix_info(stream, "operator", false);
// heuristic to find arguments tokens
// skip forward, skipping inside brackets
auto type_start = stream.cur();
@ -715,6 +742,7 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_conversion_op(const detail::parse_
cpp_conversion_op::builder builder("operator " + type_spelling,
detail::parse_type(context, cur, type));
context.comments.match(builder.get(), cur);
builder.get().add_attribute(prefix.attributes);
if (prefix.is_explicit)
builder.is_explicit();
else if (prefix.is_constexpr)
@ -745,6 +773,8 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_constructor(const detail::parse_co
cpp_constructor::builder builder(name.c_str());
context.comments.match(builder.get(), cur);
add_parameters(context, builder, cur);
builder.get().add_attribute(prefix.attributes);
if (clang_Cursor_isVariadic(cur))
builder.is_variadic();
if (prefix.is_constexpr)
@ -755,6 +785,7 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_constructor(const detail::parse_co
skip_parameters(stream);
auto suffix = parse_suffix_info(stream, context, false, false);
builder.get().add_attribute(suffix.attributes);
if (suffix.noexcept_condition)
builder.noexcept_condition(std::move(suffix.noexcept_condition));
@ -780,6 +811,7 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_destructor(const detail::parse_con
auto name = std::string("~") + stream.get().c_str();
cpp_destructor::builder builder(std::move(name));
context.comments.match(builder.get(), cur);
builder.get().add_attribute(prefix_info.attributes);
detail::skip(stream, "(");
detail::skip(stream, ")");

View file

@ -25,7 +25,7 @@ namespace
is_inline = true;
skip(stream, "namespace");
skip_attribute(stream);
auto attributes = parse_attributes(stream);
// <identifier> {
// or when anonymous: {
@ -33,9 +33,15 @@ namespace
return cpp_namespace::builder("", is_inline);
auto& name = stream.get().value();
skip_attribute(stream);
auto other_attributes = parse_attributes(stream);
attributes.insert(attributes.end(), other_attributes.begin(), other_attributes.end());
skip(stream, "{");
return cpp_namespace::builder(name.c_str(), is_inline);
auto result = cpp_namespace::builder(name.c_str(), is_inline);
result.get().add_attribute(attributes);
return result;
}
}

View file

@ -66,7 +66,8 @@ namespace cppast
};
// parse default value of variable, function parameter...
std::unique_ptr<cpp_expression> parse_default_value(const parse_context& context,
std::unique_ptr<cpp_expression> parse_default_value(cpp_attribute_list& attributes,
const parse_context& context,
const CXCursor& cur, const char* name);
std::unique_ptr<cpp_type> parse_type(const parse_context& context, const CXCursor& cur,

View file

@ -85,7 +85,9 @@ namespace
auto name = detail::get_cursor_name(cur);
auto type = clang_getCursorType(cur);
auto def = detail::parse_default_value(context, cur, name.c_str());
cpp_attribute_list attributes;
auto def = detail::parse_default_value(attributes, context, cur, name.c_str());
detail::cxtokenizer tokenizer(context.tu, context.file, cur);
detail::cxtoken_stream stream(tokenizer, cur);
@ -108,10 +110,13 @@ namespace
break;
}
return cpp_non_type_template_parameter::build(*context.idx, detail::get_entity_id(cur),
name.c_str(),
detail::parse_type(context, cur, type),
is_variadic, std::move(def));
auto result =
cpp_non_type_template_parameter::build(*context.idx, detail::get_entity_id(cur),
name.c_str(),
detail::parse_type(context, cur, type),
is_variadic, std::move(def));
result->add_attribute(attributes);
return std::move(result);
}
std::unique_ptr<cpp_template_template_parameter> parse_template_parameter(
@ -196,6 +201,17 @@ namespace
builder.add_parameter(parse_template_parameter(context, child));
});
}
void handle_comment_attributes(cpp_entity& templ_entity, cpp_entity& non_template)
{
// steal comment
auto comment = type_safe::copy(non_template.comment());
non_template.set_comment(type_safe::nullopt);
templ_entity.set_comment(std::move(comment));
// copy attributes over
templ_entity.add_attribute(non_template.attributes());
}
}
std::unique_ptr<cpp_entity> detail::parse_cpp_alias_template(const detail::parse_context& context,
@ -210,6 +226,7 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_alias_template(const detail::parse
if (!builder)
return nullptr;
context.comments.match(builder.value().get(), cur);
builder.value().get().add_attribute(builder.value().get().begin()->attributes());
parse_parameters(builder.value(), context, cur);
return builder.value().finish(*context.idx, detail::get_entity_id(cur),
false); // not a definition
@ -245,15 +262,12 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_function_template(
if (!func)
return nullptr;
// steal comment
auto comment = type_safe::copy(func->comment());
func->set_comment(type_safe::nullopt);
auto func_ptr = func.get();
cpp_function_template::builder builder(
std::unique_ptr<cpp_function_base>(static_cast<cpp_function_base*>(func.release())));
builder.get().set_comment(std::move(comment));
parse_parameters(builder, context, cur);
handle_comment_attributes(builder.get(), *func_ptr);
return builder.finish(*context.idx, detail::get_entity_id(cur),
builder.get().function().is_definition());
}
@ -315,16 +329,13 @@ std::unique_ptr<cpp_entity> detail::try_parse_cpp_function_template_specializati
}
if (!func)
return nullptr;
// steal comment
auto comment = type_safe::copy(func->comment());
func->set_comment(type_safe::nullopt);
auto func_ptr = func.get();
cpp_function_template_specialization::builder
builder(std::unique_ptr<cpp_function_base>(static_cast<cpp_function_base*>(func.release())),
cpp_template_ref(detail::get_entity_id(templ), ""));
builder.get().set_comment(std::move(comment));
parse_arguments(builder, context, cur);
handle_comment_attributes(builder.get(), *func_ptr);
return builder.finish(*context.idx, detail::get_entity_id(cur),
builder.get().function().is_definition());
}
@ -337,14 +348,11 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_class_template(const detail::parse
auto c = detail::parse_cpp_class(context, cur, clang_getNullCursor());
if (!c)
return nullptr;
// steal comment
auto comment = type_safe::copy(c->comment());
c->set_comment(type_safe::nullopt);
auto c_ptr = c.get();
cpp_class_template::builder builder(
std::unique_ptr<cpp_class>(static_cast<cpp_class*>(c.release())));
builder.get().set_comment(std::move(comment));
handle_comment_attributes(builder.get(), *c_ptr);
parse_parameters(builder, context, cur);
return builder.finish(*context.idx, detail::get_entity_id(cur),
builder.get().class_().is_definition());
@ -377,15 +385,12 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_class_template_specialization(
auto c = detail::parse_cpp_class(context, cur, clang_getNullCursor());
if (!c)
return nullptr;
// steal comment
auto comment = type_safe::copy(c->comment());
c->set_comment(type_safe::nullopt);
auto c_ptr = c.get();
cpp_class_template_specialization::builder
builder(std::unique_ptr<cpp_class>(static_cast<cpp_class*>(c.release())),
cpp_template_ref(detail::get_entity_id(primary), ""));
builder.get().set_comment(std::move(comment));
handle_comment_attributes(builder.get(), *c_ptr);
parse_parameters(builder, context, cur);
parse_arguments(builder, context, cur);
return builder.finish(*context.idx, detail::get_entity_id(cur),

View file

@ -749,13 +749,25 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_type_alias(const detail::parse_con
auto type = parse_type(context, clang_Cursor_isNull(template_cur) ? cur : template_cur,
clang_getTypedefDeclUnderlyingType(cur));
std::unique_ptr<cpp_type_alias> result;
if (!clang_Cursor_isNull(template_cur))
return cpp_type_alias::build(name.c_str(), std::move(type));
result = cpp_type_alias::build(name.c_str(), std::move(type));
else
{
auto result =
result =
cpp_type_alias::build(*context.idx, get_entity_id(cur), name.c_str(), std::move(type));
context.comments.match(*result, cur);
return std::move(result);
}
// look for attributes
detail::cxtokenizer tokenizer(context.tu, context.file, cur);
detail::cxtoken_stream stream(tokenizer, cur);
if (detail::skip_if(stream, "using"))
{
// syntax: using <identifier> attributes
detail::skip(stream, name.c_str());
result->add_attribute(detail::parse_attributes(stream));
}
return std::move(result);
}

View file

@ -11,7 +11,8 @@
using namespace cppast;
std::unique_ptr<cpp_expression> detail::parse_default_value(const detail::parse_context& context,
std::unique_ptr<cpp_expression> detail::parse_default_value(cpp_attribute_list& attributes,
const detail::parse_context& context,
const CXCursor& cur, const char* name)
{
detail::cxtokenizer tokenizer(context.tu, context.file, cur);
@ -36,7 +37,10 @@ std::unique_ptr<cpp_expression> detail::parse_default_value(const detail::parse_
break;
}
else
stream.bump();
{
auto cur_attributes = detail::parse_attributes(stream, true);
attributes.insert(attributes.end(), cur_attributes.begin(), cur_attributes.end());
}
}
if (has_default)
return parse_raw_expression(context, stream, stream.end(),
@ -65,10 +69,12 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_variable(const detail::parse_conte
else if (token.value() == "constexpr")
is_constexpr = true;
cpp_attribute_list attributes;
auto default_value = parse_default_value(attributes, context, cur, name.c_str());
std::unique_ptr<cpp_variable> result;
if (clang_isCursorDefinition(cur))
{
auto default_value = parse_default_value(context, cur, name.c_str());
result =
cpp_variable::build(*context.idx, get_entity_id(cur), name.c_str(), std::move(type),
std::move(default_value), storage_class, is_constexpr);
@ -77,6 +83,7 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_variable(const detail::parse_conte
result = cpp_variable::build_declaration(get_entity_id(cur), name.c_str(), std::move(type),
storage_class, is_constexpr);
context.comments.match(*result, cur);
result->add_attribute(attributes);
return std::move(result);
}
@ -89,6 +96,9 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_member_variable(const detail::pars
auto type = parse_type(context, cur, clang_getCursorType(cur));
auto is_mutable = clang_CXXField_isMutable(cur) != 0u;
cpp_attribute_list attributes;
auto default_value = parse_default_value(attributes, context, cur, name.c_str());
std::unique_ptr<cpp_member_variable_base> result;
if (clang_Cursor_isBitField(cur))
{
@ -102,10 +112,10 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_member_variable(const detail::pars
}
else
{
auto default_value = parse_default_value(context, cur, name.c_str());
result = cpp_member_variable::build(*context.idx, get_entity_id(cur), name.c_str(),
std::move(type), std::move(default_value), is_mutable);
}
result->add_attribute(attributes);
context.comments.match(*result, cur);
return std::move(result);
}

View file

@ -11,6 +11,7 @@ endif()
set(tests
code_generator.cpp
cpp_alias_template.cpp
cpp_attribute.cpp
cpp_class.cpp
cpp_class_template.cpp
cpp_enum.cpp

237
test/cpp_attribute.cpp Normal file
View file

@ -0,0 +1,237 @@
// 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_attribute.hpp>
#include <cppast/cpp_function.hpp>
#include <cppast/cpp_variable.hpp>
#include "test_parser.hpp"
using namespace cppast;
TEST_CASE("cpp_attribute")
{
auto code = R"(
// multiple attributes
[[attribute1]] void [[attribute2]] a();
[[attribute1, attribute2]] void b();
// variadic attributes - not actually supported by clang
//[[variadic...]] void c();
// scoped attributes
[[ns::attribute]] void d();
// argument attributes
[[attribute(arg1, arg2, +(){}, 42, "Hello!")]] void e();
// all of the above
[[ns::attribute(+, -, 0 4), other_attribute]] void f();
// known attributes
[[deprecated]] void g();
[[maybe_unused]] void h();
[[nodiscard]] int i();
[[noreturn]] void j();
// alignas
struct alignas(8) type {};
alignas(type) int var;
)";
auto file = parse({}, "cpp_attribute.cpp", code);
auto check_attribute = [](const cpp_attribute& attr, const char* name,
type_safe::optional<std::string> scope, bool variadic,
const char* args, cpp_attribute_kind kind) {
REQUIRE(attr.kind() == kind);
REQUIRE(attr.name() == name);
REQUIRE(attr.scope() == scope);
REQUIRE(attr.is_variadic() == variadic);
if (attr.arguments())
REQUIRE(attr.arguments().value().as_string() == args);
else
REQUIRE(*args == '\0');
};
auto count =
test_visit<cpp_function>(*file,
[&](const cpp_entity& e) {
auto& attributes = e.attributes();
REQUIRE(attributes.size() >= 1u);
auto& attr = attributes.front();
if (e.name() == "a" || e.name() == "b")
{
REQUIRE(attributes.size() == 2u);
REQUIRE(has_attribute(e, "attribute1"));
REQUIRE(has_attribute(e, "attribute2"));
check_attribute(attr, "attribute1", type_safe::nullopt,
false, "", cpp_attribute_kind::unknown);
check_attribute(attributes[1u], "attribute2",
type_safe::nullopt, false, "",
cpp_attribute_kind::unknown);
}
else if (e.name() == "c")
check_attribute(attr, "variadic", type_safe::nullopt, true,
"", cpp_attribute_kind::unknown);
else if (e.name() == "d")
{
REQUIRE(has_attribute(e, "ns::attribute"));
check_attribute(attr, "attribute", "ns", false, "",
cpp_attribute_kind::unknown);
}
else if (e.name() == "e")
check_attribute(attr, "attribute", type_safe::nullopt,
false, R"(arg1,arg2,+(){},42,"Hello!")",
cpp_attribute_kind::unknown);
else if (e.name() == "f")
{
REQUIRE(attributes.size() == 2u);
check_attribute(attr, "attribute", "ns", false, "+,-,0 4",
cpp_attribute_kind::unknown);
check_attribute(attributes[1u], "other_attribute",
type_safe::nullopt, false, "",
cpp_attribute_kind::unknown);
}
else if (e.name() == "g")
{
REQUIRE(has_attribute(e, cpp_attribute_kind::deprecated));
check_attribute(attr, "deprecated", type_safe::nullopt,
false, "", cpp_attribute_kind::deprecated);
}
else if (e.name() == "h")
check_attribute(attr, "maybe_unused", type_safe::nullopt,
false, "",
cpp_attribute_kind::maybe_unused);
else if (e.name() == "i")
check_attribute(attr, "nodiscard", type_safe::nullopt,
false, "", cpp_attribute_kind::nodiscard);
else if (e.name() == "j")
check_attribute(attr, "noreturn", type_safe::nullopt,
false, "", cpp_attribute_kind::noreturn);
},
false);
REQUIRE(count == 9);
count = test_visit<cpp_class>(*file,
[&](const cpp_entity& e) {
auto& attributes = e.attributes();
REQUIRE(attributes.size() == 1u);
auto& attr = attributes.front();
check_attribute(attr, "alignas", type_safe::nullopt, false,
"8", cpp_attribute_kind::alignas_);
},
false);
REQUIRE(count == 1u);
count = test_visit<cpp_variable>(*file,
[&](const cpp_entity& e) {
auto& attributes = e.attributes();
REQUIRE(attributes.size() == 1u);
auto& attr = attributes.front();
check_attribute(attr, "alignas", type_safe::nullopt, false,
"type", cpp_attribute_kind::alignas_);
},
false);
REQUIRE(count == 1u);
}
TEST_CASE("cpp_attribute matching")
{
auto code = R"(
// classes
struct [[a]] a {};
class [[b]] b {};
template <typename T>
class [[c]] c {};
template <typename T>
class [[c]] c<T*> {};
template <>
class [[c]] c<int> {};
// enums
enum [[e]] e {};
enum class [[f]] f
{
a [[a]],
b [[b]] = 42,
};
// functions
[[g]] void g();
void [[h]] h();
void i [[i]] ();
void j() [[j]];
auto k() -> int [[k]];
struct [[member_functions]] member_functions
{
void a() [[a]];
void b() const && [[b]];
virtual void c() [[c]] final;
virtual void d() [[d]] = 0;
[[member_functions]] member_functions();
member_functions(const member_functions&) [[member_functions]];
};
// variables
[[l]] const int l = 42;
static void* [[m]] m;
void [[function_params]] function_params
([[a]] int a, int [[b]] b, int c [[c]] = 42);
struct [[members]] members
{
int [[a]] a;
int [[b]] b : 2;
};
struct [[bases]] bases
: [[a]] public a,
[[members]] members
{};
// namespace
namespace [[n]] n {}
// type aliases
using o [[o]] = int;
template <typename T>
using p [[p]] = T;
)";
auto file = parse({}, "cpp_attribute__matching.cpp", code);
auto count = 0u;
auto check = [&](const cppast::cpp_entity& e) {
INFO(e.name());
REQUIRE(e.attributes().size() == 1u);
REQUIRE(e.attributes().begin()->name() == e.name());
++count;
};
visit(*file, [&](const cppast::cpp_entity& e, const cppast::visitor_info& info) {
if (info.event != cppast::visitor_info::container_entity_exit
&& e.kind() != cppast::cpp_file::kind() && !is_friended(e) && !is_templated(e))
{
check(e);
if (e.kind() == cppast::cpp_function::kind())
for (auto& param : static_cast<const cppast::cpp_function&>(e).parameters())
check(param);
else if (e.kind() == cppast::cpp_class::kind())
for (auto& base : static_cast<const cppast::cpp_class&>(e).bases())
check(base);
}
return true;
});
REQUIRE(count == 36u);
}

View file

@ -92,6 +92,12 @@ class a<T*> {};
/// };
template <typename T>
struct b<0, T> {};
// extern template for good measure
// currently not really supported but parsing must not fail
/// template<>
/// class a<int>;
extern template class a<int>;
)";
cpp_entity_index idx;
@ -263,5 +269,5 @@ struct b<0, T> {};
else
REQUIRE(false);
});
REQUIRE(count == 4u);
REQUIRE(count == 5u);
}