From 8691628ec5f2c33a5be0fd38134454356c71cba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Sun, 12 Mar 2017 17:43:57 +0100 Subject: [PATCH] Parse cpp_converison_op --- include/cppast/cpp_member_function.hpp | 28 +++++++-- src/cpp_member_function.cpp | 7 ++- src/libclang/function_parser.cpp | 78 ++++++++++++++++++++++---- src/libclang/parse_functions.cpp | 2 + src/libclang/parse_functions.hpp | 2 + src/libclang/tokenizer.cpp | 30 ++++++++-- src/libclang/tokenizer.hpp | 3 +- test/cpp_member_function.cpp | 55 ++++++++++++++++++ test/cpp_type_alias.cpp | 4 +- 9 files changed, 184 insertions(+), 25 deletions(-) diff --git a/include/cppast/cpp_member_function.hpp b/include/cppast/cpp_member_function.hpp index 4b221a2..d28750d 100644 --- a/include/cppast/cpp_member_function.hpp +++ b/include/cppast/cpp_member_function.hpp @@ -146,6 +146,9 @@ namespace cppast { static_cast(*this->function).constexpr_ = true; } + + protected: + basic_member_builder() noexcept = default; }; /// \effects Sets name and return type, as well as the rest to defaults. @@ -173,10 +176,11 @@ namespace cppast static cpp_entity_kind kind() noexcept; /// Builder for [cppast::cpp_member_function](). - class builder : public basic_member_builder + class builder : public cpp_member_function_base::basic_member_builder { public: - using basic_member_builder::basic_member_builder; + using cpp_member_function_base::basic_member_builder:: + basic_member_builder; }; private: @@ -191,17 +195,29 @@ namespace cppast class cpp_conversion_op final : public cpp_member_function_base { public: + static cpp_entity_kind kind() noexcept; + /// Builder for [cppast::cpp_conversion_op](). class builder : public basic_member_builder { public: - using basic_member_builder::basic_member_builder; + /// \effects Creates it giving it the return type. + /// \notes It does not have a name as it is given by the return type. + builder(std::unique_ptr type) + { + function = + std::unique_ptr(new cpp_conversion_op(std::move(type))); + } /// \effects Marks the conversion operator `explicit`. void is_explicit() noexcept { function->explicit_ = true; } + + private: + using basic_member_builder::is_variadic; + using basic_member_builder::add_parameter; }; /// \returns Whether or not the conversion is `explicit`. @@ -211,14 +227,16 @@ namespace cppast } private: - cpp_conversion_op(std::string name, std::unique_ptr return_t) - : cpp_member_function_base(std::move(name), std::move(return_t)), explicit_(false) + cpp_conversion_op(std::unique_ptr return_t) + : cpp_member_function_base("", std::move(return_t)), explicit_(false) { } cpp_entity_kind do_get_entity_kind() const noexcept override; bool explicit_; + + friend basic_member_builder; }; /// A [cppast::cpp_entity]() modelling a C++ constructor. diff --git a/src/cpp_member_function.cpp b/src/cpp_member_function.cpp index a867440..074fba1 100644 --- a/src/cpp_member_function.cpp +++ b/src/cpp_member_function.cpp @@ -18,11 +18,16 @@ cpp_entity_kind cpp_member_function::do_get_entity_kind() const noexcept return kind(); } -cpp_entity_kind cpp_conversion_op::do_get_entity_kind() const noexcept +cpp_entity_kind cpp_conversion_op::kind() noexcept { return cpp_entity_kind::conversion_op_t; } +cpp_entity_kind cpp_conversion_op::do_get_entity_kind() const noexcept +{ + return kind(); +} + cpp_entity_kind cpp_constructor::do_get_entity_kind() const noexcept { return cpp_entity_kind::constructor_t; diff --git a/src/libclang/function_parser.cpp b/src/libclang/function_parser.cpp index d67c6af..7fb7e42 100644 --- a/src/libclang/function_parser.cpp +++ b/src/libclang/function_parser.cpp @@ -66,7 +66,8 @@ namespace prefix_info result; // just check for keywords until we've reached the function name - while (!detail::skip_if(stream, name.c_str())) + // notes: name can have multiple tokens if it is an operator + while (!detail::skip_if(stream, name.c_str(), true)) { if (detail::skip_if(stream, "constexpr")) result.is_constexpr = true; @@ -270,7 +271,7 @@ namespace auto prefix = parse_prefix_info(stream, name); DEBUG_ASSERT(!prefix.is_virtual, detail::parse_error_handler{}, cur, - "unexpected tokens in function prefix"); + "free function cannot be virtual"); if (prefix.is_constexpr) builder.is_constexpr(); @@ -356,6 +357,21 @@ namespace return result; } } + + template + std::unique_ptr handle_suffix(const detail::parse_context& context, + const CXCursor& cur, Builder& builder, + detail::token_stream& stream, bool is_virtual) + { + auto suffix = parse_suffix_info(stream, context); + builder.cv_ref_qualifier(suffix.cv_qualifier, suffix.ref_qualifier); + if (suffix.noexcept_condition) + builder.noexcept_condition(move(suffix.noexcept_condition)); + if (auto virt = calculate_virtual(cur, is_virtual, suffix.virtual_keywords)) + builder.virtual_info(virt.value()); + + return builder.finish(*context.idx, detail::get_entity_id(cur), suffix.body_kind); + } } std::unique_ptr detail::parse_cpp_member_function(const detail::parse_context& context, @@ -379,13 +395,53 @@ std::unique_ptr detail::parse_cpp_member_function(const detail::pars builder.is_constexpr(); skip_parameters(stream); - - auto suffix = parse_suffix_info(stream, context); - builder.cv_ref_qualifier(suffix.cv_qualifier, suffix.ref_qualifier); - if (suffix.noexcept_condition) - builder.noexcept_condition(std::move(suffix.noexcept_condition)); - if (auto virt = calculate_virtual(cur, prefix.is_virtual, suffix.virtual_keywords)) - builder.virtual_info(virt.value()); - - return builder.finish(*context.idx, detail::get_entity_id(cur), suffix.body_kind); + return handle_suffix(context, cur, builder, stream, prefix.is_virtual); +} + +std::unique_ptr detail::parse_cpp_conversion_op(const detail::parse_context& context, + const CXCursor& cur) +{ + DEBUG_ASSERT(clang_getCursorKind(cur) == CXCursor_ConversionFunction, detail::assert_handler{}); + cpp_conversion_op::builder builder(detail::parse_type(context, clang_getCursorResultType(cur))); + + detail::tokenizer tokenizer(context.tu, context.file, cur); + detail::token_stream stream(tokenizer, cur); + + // look for constexpr, explicit, virtual + // must come before the operator token + auto is_virtual = false; + while (!detail::skip_if(stream, "operator")) + { + if (detail::skip_if(stream, "virtual")) + is_virtual = true; + else if (detail::skip_if(stream, "constexpr")) + builder.is_constexpr(); + else if (detail::skip_if(stream, "explicit")) + builder.is_explicit(); + else + stream.bump(); + } + + // heuristic to find arguments tokens + // skip forward, skipping inside brackets + while (true) + { + if (detail::skip_if(stream, "(")) + { + if (detail::skip_if(stream, ")")) + break; + else + detail::skip_brackets(stream); + } + else if (detail::skip_if(stream, "[")) + detail::skip_brackets(stream); + else if (detail::skip_if(stream, "{")) + detail::skip_brackets(stream); + else if (detail::skip_if(stream, "<")) + detail::skip_brackets(stream); + else + stream.bump(); + } + + return handle_suffix(context, cur, builder, stream, is_virtual); } diff --git a/src/libclang/parse_functions.cpp b/src/libclang/parse_functions.cpp index a5f5309..808db5e 100644 --- a/src/libclang/parse_functions.cpp +++ b/src/libclang/parse_functions.cpp @@ -92,6 +92,8 @@ std::unique_ptr detail::parse_entity(const detail::parse_context& co if (auto func = try_parse_static_cpp_function(context, cur)) return func; return parse_cpp_member_function(context, cur); + case CXCursor_ConversionFunction: + return parse_cpp_conversion_op(context, cur); default: break; diff --git a/src/libclang/parse_functions.hpp b/src/libclang/parse_functions.hpp index 00307e7..8d7ab58 100644 --- a/src/libclang/parse_functions.hpp +++ b/src/libclang/parse_functions.hpp @@ -89,6 +89,8 @@ namespace cppast const CXCursor& cur); std::unique_ptr parse_cpp_member_function(const parse_context& context, const CXCursor& cur); + std::unique_ptr parse_cpp_conversion_op(const parse_context& context, + const CXCursor& cur); std::unique_ptr parse_entity(const parse_context& context, const CXCursor& cur); } diff --git a/src/libclang/tokenizer.cpp b/src/libclang/tokenizer.cpp index 5a62d7c..291476c 100644 --- a/src/libclang/tokenizer.cpp +++ b/src/libclang/tokenizer.cpp @@ -170,12 +170,32 @@ void detail::skip(detail::token_stream& stream, const char* str) stream.bump(); } -bool detail::skip_if(detail::token_stream& stream, const char* str) +namespace { - auto& token = stream.peek(); - if (token != str) - return false; - stream.bump(); + bool starts_with(const char*& str, const detail::token& t) + { + if (std::strncmp(str, t.c_str(), t.value().length()) != 0) + return false; + str += t.value().length(); + while (*str == ' ' || *str == '\t') + ++str; + return true; + } +} + +bool detail::skip_if(detail::token_stream& stream, const char* str, bool multi_token) +{ + auto save = stream.cur(); + do + { + auto& token = stream.peek(); + if (!starts_with(str, token) || (!multi_token && *str != '\0')) + { + stream.set_cur(save); + return false; + } + stream.bump(); + } while (multi_token && *str); return true; } diff --git a/src/libclang/tokenizer.hpp b/src/libclang/tokenizer.hpp index ed4e9a2..4740c4c 100644 --- a/src/libclang/tokenizer.hpp +++ b/src/libclang/tokenizer.hpp @@ -154,7 +154,8 @@ namespace cppast void skip(token_stream& stream, const char* str); // skips the next token if it has the given string - bool skip_if(token_stream& stream, const char* str); + // if multi_token == true, str can consist of multiple tokens optionally separated by whitespace + bool skip_if(token_stream& stream, const char* str, bool multi_token = false); // returns the location of the closing bracket // the current token must be (,[,{ or < diff --git a/test/cpp_member_function.cpp b/test/cpp_member_function.cpp index afe2e1e..5a806dd 100644 --- a/test/cpp_member_function.cpp +++ b/test/cpp_member_function.cpp @@ -130,3 +130,58 @@ struct bar : foo }); REQUIRE(count == 12u); } + +TEST_CASE("cpp_conversion_op") +{ + auto code = R"( +namespace ns +{ + using type = char; +} + +// most of it only need to be check in member function +struct foo +{ + operator int&(); + explicit operator bool() const; + constexpr operator ns::type(); +}; +)"; + + cpp_entity_index idx; + auto file = parse(idx, "cpp_conversion_op.cpp", code); + auto count = test_visit(*file, [&](const cpp_conversion_op& op) { + REQUIRE(op.name().empty()); + REQUIRE(count_children(op) == 0u); + REQUIRE(!op.is_variadic()); + REQUIRE(op.body_kind() == cpp_function_declaration); + REQUIRE(op.ref_qualifier() == cpp_ref_none); + REQUIRE(!op.virtual_info()); + REQUIRE(!op.noexcept_condition()); + + if (!op.is_explicit() && !op.is_constexpr()) + { + REQUIRE(equal_types(idx, op.return_type(), + *cpp_reference_type::build(cpp_builtin_type::build("int"), + cpp_ref_lvalue))); + REQUIRE(op.cv_qualifier() == cpp_cv_none); + REQUIRE(!op.is_explicit()); + REQUIRE(!op.is_constexpr()); + } + else if (op.is_explicit() && !op.is_constexpr()) + { + REQUIRE(equal_types(idx, op.return_type(), *cpp_builtin_type::build("bool"))); + REQUIRE(op.cv_qualifier() == cpp_cv_const); + } + else if (!op.is_explicit() && op.is_constexpr()) + { + REQUIRE(equal_types(idx, op.return_type(), + *cpp_user_defined_type::build( + cpp_type_ref(cpp_entity_id(""), "ns::type")))); + REQUIRE(op.cv_qualifier() == cpp_cv_none); + } + else + REQUIRE(false); + }); + REQUIRE(count == 3u); +} diff --git a/test/cpp_type_alias.cpp b/test/cpp_type_alias.cpp index a0fe92e..d11e3f4 100644 --- a/test/cpp_type_alias.cpp +++ b/test/cpp_type_alias.cpp @@ -29,8 +29,8 @@ bool equal_types(const cpp_entity_index& idx, const cpp_type& parsed, const cpp_ if (user_parsed.name() != user_synthesized.name()) return false; auto entity = user_parsed.get(idx); - return entity.has_value() && entity.value().name().empty() - || entity.value().name() == user_parsed.name(); + return entity.has_value() && (entity.value().name().empty() + || full_name(entity.value()) == user_parsed.name()); } case cpp_type_kind::cv_qualified: