Parse free functions

This commit is contained in:
Jonathan Müller 2017-03-11 18:08:06 +01:00
commit 9267bbbff2
15 changed files with 442 additions and 58 deletions

View file

@ -17,6 +17,8 @@ namespace cppast
class cpp_function_parameter final : public cpp_entity, public cpp_variable_base
{
public:
static cpp_entity_kind kind() noexcept;
/// \returns A newly created and registered function parameter.
static std::unique_ptr<cpp_function_parameter> build(
const cpp_entity_index& idx, cpp_entity_id id, std::string name,
@ -121,6 +123,7 @@ namespace cppast
std::unique_ptr<T> finish(const cpp_entity_index& idx, cpp_entity_id id,
cpp_function_body_kind body_kind)
{
function->body_ = body_kind;
if (cppast::is_definition(body_kind))
idx.register_entity(std::move(id), type_safe::cref(*function));
else
@ -161,6 +164,8 @@ namespace cppast
class cpp_function final : public cpp_function_base
{
public:
static cpp_entity_kind kind() noexcept;
/// Builds a [cppast::cpp_function]().
class builder : public cpp_function_base::basic_builder<cpp_function>
{

View file

@ -12,7 +12,7 @@ namespace cppast
/// See http://en.cppreference.com/w/cpp/language/storage_duration, for example.
/// \notes These are just all the possible *keywords* used in a variable declaration,
/// not necessarily their *semantic* meaning.
enum cpp_storage_class_specifiers
enum cpp_storage_class_specifiers : int
{
cpp_storage_class_none = 0, //< no storage class specifier given.

View file

@ -8,6 +8,11 @@
using namespace cppast;
cpp_entity_kind cpp_function_parameter::kind() noexcept
{
return cpp_entity_kind::function_parameter_t;
}
std::unique_ptr<cpp_function_parameter> cpp_function_parameter::build(
const cpp_entity_index& idx, cpp_entity_id id, std::string name, std::unique_ptr<cpp_type> type,
std::unique_ptr<cpp_expression> def)
@ -20,10 +25,15 @@ std::unique_ptr<cpp_function_parameter> cpp_function_parameter::build(
cpp_entity_kind cpp_function_parameter::do_get_entity_kind() const noexcept
{
return cpp_entity_kind::function_parameter_t;
return kind();
}
cpp_entity_kind cpp_function::kind() noexcept
{
return cpp_entity_kind::function_t;
}
cpp_entity_kind cpp_function::do_get_entity_kind() const noexcept
{
return cpp_entity_kind::function_t;
return kind();
}

View file

@ -38,11 +38,11 @@ void detail::print_cursor_info(const CXCursor& cur) noexcept
cxstring(clang_getCursorKindSpelling(cur.kind)).c_str());
}
void detail::print_tokens(const detail::cxtranslation_unit& tu, const CXFile& file,
void detail::print_tokens(const CXTranslationUnit& tu, const CXFile& file,
const CXCursor& cur) noexcept
{
std::lock_guard<std::mutex> lock(mtx);
detail::tokenizer tokenizer(tu.get(), file, cur);
detail::tokenizer tokenizer(tu, file, cur);
for (auto& token : tokenizer)
std::printf("%s ", token.c_str());
std::puts("\n");

View file

@ -19,7 +19,7 @@ namespace cppast
void print_cursor_info(const CXCursor& cur) noexcept;
void print_tokens(const cxtranslation_unit& tu, const CXFile& file,
void print_tokens(const CXTranslationUnit& tu, const CXFile& file,
const CXCursor& cur) noexcept;
}
} // namespace cppast::detail

View file

@ -10,11 +10,11 @@ using namespace cppast;
namespace
{
std::string get_expression_str(detail::token_stream& stream)
std::string get_expression_str(detail::token_stream& stream, detail::token_iterator end)
{
// just concat everything
std::string expr;
while (!stream.done())
while (stream.cur() != end)
expr += stream.get().c_str();
return expr;
}
@ -30,7 +30,7 @@ std::unique_ptr<cpp_expression> detail::parse_expression(const detail::parse_con
detail::token_stream stream(tokenizer, cur);
auto type = parse_type(context, clang_getCursorType(cur));
auto expr = get_expression_str(stream);
auto expr = get_expression_str(stream, stream.end());
if (kind == CXCursor_CallExpr && (expr.empty() || expr.back() != ')'))
{
// we have a call expression that doesn't end in a closing parentheses
@ -47,12 +47,13 @@ std::unique_ptr<cpp_expression> detail::parse_expression(const detail::parse_con
return cpp_unexposed_expression::build(std::move(type), std::move(expr));
}
std::unique_ptr<cpp_expression> detail::parse_raw_expression(const parse_context& context,
token_stream& stream,
const CXType& type)
std::unique_ptr<cpp_expression> detail::parse_raw_expression(const parse_context&,
token_stream& stream,
token_iterator end,
std::unique_ptr<cpp_type> type)
{
if (stream.done())
return nullptr;
auto expr = get_expression_str(stream);
return cpp_unexposed_expression::build(parse_type(context, type), std::move(expr));
auto expr = get_expression_str(stream, end);
return cpp_unexposed_expression::build(std::move(type), std::move(expr));
}

View file

@ -0,0 +1,128 @@
// 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_function.hpp>
#include "libclang_visitor.hpp"
#include "parse_functions.hpp"
using namespace cppast;
namespace
{
std::unique_ptr<cpp_function_parameter> parse_parameter(const detail::parse_context& context,
const CXCursor& cur)
{
auto name = detail::get_cursor_name(cur);
auto type = detail::parse_type(context, clang_getCursorType(cur));
std::unique_ptr<cpp_expression> default_value;
detail::visit_children(cur, [&](const CXCursor& child) {
DEBUG_ASSERT(clang_isExpression(child.kind) && !default_value,
detail::parse_error_handler{}, child,
"unexpected child cursor of function parameter");
default_value = detail::parse_expression(context, child);
});
return cpp_function_parameter::build(*context.idx, detail::get_entity_id(cur), name.c_str(),
std::move(type), std::move(default_value));
}
template <class Builder>
void add_parameters(const detail::parse_context& context, Builder& builder, const CXCursor& cur)
{
detail::visit_children(cur, [&](const CXCursor& child) {
if (clang_getCursorKind(child) != CXCursor_ParmDecl)
return;
try
{
auto parameter = parse_parameter(context, child);
builder.add_parameter(std::move(parameter));
}
catch (detail::parse_error& ex)
{
context.logger->log("libclang parser", ex.get_diagnostic());
}
});
}
void skip_parameters(detail::token_stream& stream)
{
detail::skip_brackets(stream);
}
std::unique_ptr<cpp_expression> try_parse_noexcept(detail::token_stream& stream,
const detail::parse_context& context)
{
if (!detail::skip_if(stream, "noexcept"))
return nullptr;
auto type = cpp_builtin_type::build("bool");
if (stream.peek().value() != "(")
return cpp_literal_expression::build(std::move(type), "true");
auto closing = detail::find_closing_bracket(stream);
detail::skip(stream, "(");
auto expr = detail::parse_raw_expression(context, stream, closing, std::move(type));
detail::skip(stream, ")");
return expr;
}
cpp_function_body_kind parse_body_kind(detail::token_stream& stream)
{
if (detail::skip_if(stream, "default"))
return cpp_function_defaulted;
else if (detail::skip_if(stream, "delete"))
return cpp_function_deleted;
DEBUG_UNREACHABLE(detail::parse_error_handler{}, stream.cursor(),
"unexpected token for function body kind");
return cpp_function_declaration;
}
}
std::unique_ptr<cpp_entity> detail::parse_cpp_function(const detail::parse_context& context,
const CXCursor& cur)
{
DEBUG_ASSERT(clang_getCursorKind(cur) == CXCursor_FunctionDecl, detail::assert_handler{});
auto name = detail::get_cursor_name(cur);
cpp_function::builder builder(name.c_str(),
detail::parse_type(context, clang_getCursorResultType(cur)));
add_parameters(context, builder, cur);
if (clang_Cursor_isVariadic(cur))
builder.is_variadic();
builder.storage_class(detail::get_storage_class(cur));
detail::tokenizer tokenizer(context.tu, context.file, cur);
detail::token_stream stream(tokenizer, cur);
// parse prefix
while (!detail::skip_if(stream, name.c_str()))
{
if (detail::skip_if(stream, "constexpr"))
builder.is_constexpr();
else
stream.bump();
}
// skip parameters
skip_parameters(stream);
auto body = clang_isCursorDefinition(cur) ? cpp_function_definition : cpp_function_declaration;
// parse suffix
// tokenizer only tokenizes signature, so !stream.done() is sufficient
while (!stream.done())
{
if (auto expr = try_parse_noexcept(stream, context))
builder.noexcept_condition(std::move(expr));
else if (skip_if(stream, "="))
body = parse_body_kind(stream);
else
stream.bump();
}
return builder.finish(*context.idx, detail::get_entity_id(cur), body);
}

View file

@ -156,7 +156,7 @@ namespace
}
}
return tu;
return detail::cxtranslation_unit(tu);
}
unsigned get_line_no(const CXCursor& cursor)

View file

@ -4,6 +4,8 @@
#include "parse_functions.hpp"
#include <cppast/cpp_storage_class_specifiers.hpp>
using namespace cppast;
cpp_entity_id detail::get_entity_id(const CXCursor& cur)
@ -18,6 +20,35 @@ detail::cxstring detail::get_cursor_name(const CXCursor& cur)
return cxstring(clang_getCursorSpelling(cur));
}
cpp_storage_class_specifiers detail::get_storage_class(const CXCursor& cur)
{
switch (clang_Cursor_getStorageClass(cur))
{
case CX_SC_Invalid:
break;
case CX_SC_None:
return cpp_storage_class_none;
case CX_SC_Auto:
case CX_SC_Register:
return cpp_storage_class_auto;
case CX_SC_Extern:
return cpp_storage_class_extern;
case CX_SC_Static:
return cpp_storage_class_static;
case CX_SC_PrivateExtern:
case CX_SC_OpenCLWorkGroupLocal:
// non-exposed storage classes
return cpp_storage_class_auto;
}
DEBUG_UNREACHABLE(detail::parse_error_handler{}, cur, "unexpected storage class");
return cpp_storage_class_auto;
}
std::unique_ptr<cpp_entity> detail::parse_entity(const detail::parse_context& context,
const CXCursor& cur) try
{
@ -54,6 +85,9 @@ std::unique_ptr<cpp_entity> detail::parse_entity(const detail::parse_context& co
case CXCursor_FieldDecl:
return parse_cpp_member_variable(context, cur);
case CXCursor_FunctionDecl:
return parse_cpp_function(context, cur);
default:
break;
}

View file

@ -16,6 +16,7 @@ namespace cppast
{
class cpp_expression;
class cpp_type;
enum cpp_storage_class_specifiers : int;
namespace detail
{
@ -26,6 +27,9 @@ namespace cppast
// as then you won't get it "as-is"
cxstring get_cursor_name(const CXCursor& cur);
// note: does not handle thread_local
cpp_storage_class_specifiers get_storage_class(const CXCursor& cur);
struct parse_context
{
CXTranslationUnit tu;
@ -39,12 +43,13 @@ namespace cppast
std::unique_ptr<cpp_expression> parse_expression(const parse_context& context,
const CXCursor& cur);
// parse the expression starting at the current token in the stream
// and ends at the end of the stream
// and ends at the given iterator
// this is required for situations where there is no expression cursor exposed,
// like member initializers
std::unique_ptr<cpp_expression> parse_raw_expression(const parse_context& context,
token_stream& stream,
const CXType& type);
std::unique_ptr<cpp_expression> parse_raw_expression(const parse_context& context,
token_stream& stream,
token_iterator end,
std::unique_ptr<cpp_type> type);
// parse_entity() dispatches on the cursor type
// it calls one of the other parse functions defined elsewhere
@ -76,6 +81,9 @@ namespace cppast
std::unique_ptr<cpp_entity> parse_cpp_member_variable(const parse_context& context,
const CXCursor& cur);
std::unique_ptr<cpp_entity> parse_cpp_function(const parse_context& context,
const CXCursor& cur);
std::unique_ptr<cpp_entity> parse_entity(const parse_context& context, const CXCursor& cur);
}
} // namespace cppast::detail

View file

@ -28,7 +28,7 @@ namespace cppast
{
}
raii_wrapper(T obj) noexcept : obj_(obj)
explicit raii_wrapper(T obj) noexcept : obj_(obj)
{
DEBUG_ASSERT(obj_, detail::assert_handler{});
}

View file

@ -124,11 +124,21 @@ namespace cppast
return cursor_;
}
token_iterator begin() const noexcept
{
return begin_;
}
token_iterator cur() const noexcept
{
return cur_;
}
token_iterator end() const noexcept
{
return end_;
}
void set_cur(token_iterator iter) noexcept
{
cur_ = iter;

View file

@ -183,6 +183,9 @@ namespace
return cpp_ref_none;
}
std::unique_ptr<cpp_type> parse_type_impl(const detail::parse_context& context,
const CXType& type);
std::unique_ptr<cpp_expression> parse_array_size(const CXType& type)
{
auto size = clang_getArraySize(type);
@ -214,8 +217,27 @@ namespace
size_expr.rend()));
}
std::unique_ptr<cpp_type> parse_type_impl(const detail::parse_context& context,
const CXType& type);
std::unique_ptr<cpp_type> try_parse_array_type(const detail::parse_context& context,
const CXType& type)
{
auto canonical = clang_getCanonicalType(type);
auto value_type = clang_getArrayElementType(type);
if (value_type.kind == CXType_Invalid)
{
// value_type is invalid, however type can still be an array
// as there seems to be a libclang bug
// only if the canonical type is not an array,
// is it truly not an array
value_type = clang_getArrayElementType(canonical);
if (value_type.kind == CXType_Invalid)
return nullptr;
// we have an array, even though type isn't one directly
// only downside of this workaround: we've stripped away typedefs
}
auto size = parse_array_size(canonical); // type may not work, see above
return cpp_array_type::build(parse_type_impl(context, value_type), std::move(size));
}
template <class Builder>
std::unique_ptr<cpp_type> add_parameters(Builder& builder, const detail::parse_context& context,
@ -317,6 +339,9 @@ namespace
// guess what: after you've called clang_getPointeeType() on a function pointer
// you'll get an unexposed type
return ftype;
else if (auto atype = try_parse_array_type(context, type))
// same deal here
return atype;
return cpp_unexposed_type::build(get_type_spelling(type).c_str());
case CXType_Void:
@ -379,11 +404,7 @@ namespace
case CXType_VariableArray:
case CXType_DependentSizedArray:
case CXType_ConstantArray:
{
auto size = parse_array_size(type);
auto value_type = parse_type_impl(context, clang_getArrayElementType(type));
return cpp_array_type::build(std::move(value_type), std::move(size));
}
return try_parse_array_type(context, type);
case CXType_FunctionNoProto:
case CXType_FunctionProto:

View file

@ -28,35 +28,6 @@ namespace
});
return expression;
}
cpp_storage_class_specifiers parse_storage_class(const CXCursor& cur)
{
switch (clang_Cursor_getStorageClass(cur))
{
case CX_SC_Invalid:
break;
case CX_SC_None:
return cpp_storage_class_none;
case CX_SC_Auto:
case CX_SC_Register:
return cpp_storage_class_auto;
case CX_SC_Extern:
return cpp_storage_class_extern;
case CX_SC_Static:
return cpp_storage_class_static;
case CX_SC_PrivateExtern:
case CX_SC_OpenCLWorkGroupLocal:
// non-exposed storage classes
return cpp_storage_class_auto;
}
DEBUG_UNREACHABLE(detail::parse_error_handler{}, cur, "unexpected storage class");
return cpp_storage_class_auto;
}
}
std::unique_ptr<cpp_entity> detail::parse_cpp_variable(const detail::parse_context& context,
@ -66,7 +37,7 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_variable(const detail::parse_conte
auto name = get_cursor_name(cur);
auto type = parse_type(context, clang_getCursorType(cur));
auto storage_class = parse_storage_class(cur);
auto storage_class = get_storage_class(cur);
auto is_constexpr = false;
// just look for thread local or constexpr
@ -118,7 +89,8 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_member_variable(const detail::pars
// look for the equal sign, default value starts there
while (!stream.done() && !skip_if(stream, "="))
stream.bump();
auto default_value = parse_raw_expression(context, stream, clang_getCursorType(cur));
auto default_value = parse_raw_expression(context, stream, stream.end(),
parse_type(context, clang_getCursorType(cur)));
return cpp_member_variable::build(*context.idx, get_entity_id(cur), name.c_str(),
std::move(type), std::move(default_value), is_mutable);

195
test/cpp_function.cpp Normal file
View file

@ -0,0 +1,195 @@
// 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_function.hpp>
#include <cppast/cpp_array_type.hpp>
#include "test_parser.hpp"
using namespace cppast;
TEST_CASE("cpp_function")
{
auto code = R"(
// parameters and return type are only tested here
void a();
int b(int a, float* b = nullptr);
int (&c(int a, ...))[10];
// noexcept conditions
void d() noexcept;
void e() noexcept(false);
void f() noexcept(noexcept(d()));
// storage class + constexpr
extern void g();
static void h();
constexpr void i();
static constexpr void j();
// body
void k() = delete;
void l()
{
// might confuse parser
auto b = noexcept(g());
}
)";
auto check_body = [](const cpp_function& func, cpp_function_body_kind kind) {
REQUIRE(func.body_kind() == kind);
REQUIRE(func.is_declaration() == is_declaration(kind));
REQUIRE(func.is_definition() == is_definition(kind));
};
cpp_entity_index idx;
auto file = parse(idx, "cpp_function.cpp", code);
auto count = test_visit<cpp_function>(*file, [&](const cpp_function& func) {
REQUIRE(!func.is_friend());
if (func.name() == "a" || func.name() == "b" || func.name() == "c")
{
REQUIRE(!func.noexcept_condition());
REQUIRE(func.storage_class() == cpp_storage_class_none);
REQUIRE(!func.is_constexpr());
check_body(func, cpp_function_declaration);
if (func.name() == "a")
{
REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("void")));
REQUIRE(count_children(func) == 0u);
REQUIRE(!func.is_variadic());
}
else if (func.name() == "b")
{
REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("int")));
auto count = 0u;
for (auto& param : func)
{
if (param.name() == "a")
{
REQUIRE(equal_types(idx, param.type(), *cpp_builtin_type::build("int")));
REQUIRE(!param.default_value());
}
else if (param.name() == "b")
{
REQUIRE(
equal_types(idx, param.type(), *cpp_pointer_type::build(
cpp_builtin_type::build("float"))));
REQUIRE(param.default_value());
REQUIRE(equal_expressions(param.default_value().value(),
*cpp_unexposed_expression::
build(cpp_pointer_type::build(
cpp_builtin_type::build("float")),
"nullptr")));
}
else
REQUIRE(false);
++count;
}
REQUIRE(count == 2u);
REQUIRE(!func.is_variadic());
}
else if (func.name() == "c")
{
REQUIRE(
equal_types(idx, func.return_type(),
*cpp_reference_type::
build(cpp_array_type::build(cpp_builtin_type::build("int"),
cpp_literal_expression::
build(cpp_builtin_type::build(
"unsigned long long"),
"10")),
cpp_ref_lvalue)));
auto count = 0u;
for (auto& param : func)
{
if (param.name() == "a")
{
REQUIRE(equal_types(idx, param.type(), *cpp_builtin_type::build("int")));
REQUIRE(!param.default_value());
}
else
REQUIRE(false);
++count;
}
REQUIRE(count == 1u);
REQUIRE(func.is_variadic());
}
}
else if (func.name() == "d" || func.name() == "e" || func.name() == "f")
{
REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("void")));
REQUIRE(count_children(func) == 0u);
REQUIRE(!func.is_variadic());
REQUIRE(func.storage_class() == cpp_storage_class_none);
REQUIRE(!func.is_constexpr());
REQUIRE(func.noexcept_condition());
check_body(func, cpp_function_declaration);
auto bool_t = cpp_builtin_type::build("bool");
if (func.name() == "d")
REQUIRE(
equal_expressions(func.noexcept_condition().value(),
*cpp_literal_expression::build(std::move(bool_t), "true")));
else if (func.name() == "e")
REQUIRE(equal_expressions(func.noexcept_condition().value(),
*cpp_unexposed_expression::build(std::move(bool_t),
"false")));
else if (func.name() == "f")
REQUIRE(equal_expressions(func.noexcept_condition().value(),
*cpp_unexposed_expression::build(std::move(bool_t),
"noexcept(d())")));
}
else if (func.name() == "g" || func.name() == "h" || func.name() == "i"
|| func.name() == "j")
{
REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("void")));
REQUIRE(count_children(func) == 0u);
REQUIRE(!func.is_variadic());
REQUIRE(!func.noexcept_condition());
check_body(func, cpp_function_declaration);
if (func.name() == "g")
{
REQUIRE(!func.is_constexpr());
REQUIRE(func.storage_class() == cpp_storage_class_extern);
}
else if (func.name() == "h")
{
REQUIRE(!func.is_constexpr());
REQUIRE(func.storage_class() == cpp_storage_class_static);
}
else if (func.name() == "i")
{
REQUIRE(func.is_constexpr());
REQUIRE(func.storage_class() == cpp_storage_class_none);
}
else if (func.name() == "j")
{
REQUIRE(func.is_constexpr());
REQUIRE(func.storage_class() == cpp_storage_class_static);
}
}
else if (func.name() == "k" || func.name() == "l")
{
REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("void")));
REQUIRE(count_children(func) == 0u);
REQUIRE(!func.is_variadic());
REQUIRE(!func.noexcept_condition());
REQUIRE(!func.is_constexpr());
REQUIRE(func.storage_class() == cpp_storage_class_none);
if (func.name() == "k")
check_body(func, cpp_function_deleted);
else if (func.name() == "l")
check_body(func, cpp_function_definition);
}
else
REQUIRE(false);
});
REQUIRE(count == 12u);
}