From 07788fe86548ff2910b18a1f57e42930a109a516 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 17 May 2025 17:08:04 +0200 Subject: [PATCH 1/9] feat: add parsing/Tokens.hpp --- include/ctnp/parsing/Tokens.hpp | 678 ++++++++++++++++++++++++++++++++ src/Lexer.cpp | 2 +- tests/CMakeLists.txt | 2 + tests/Lexer.cpp | 2 +- tests/parsing/CMakeLists.txt | 9 + tests/parsing/Tokens.cpp | 6 + 6 files changed, 697 insertions(+), 2 deletions(-) create mode 100644 include/ctnp/parsing/Tokens.hpp create mode 100644 tests/parsing/CMakeLists.txt create mode 100644 tests/parsing/Tokens.cpp diff --git a/include/ctnp/parsing/Tokens.hpp b/include/ctnp/parsing/Tokens.hpp new file mode 100644 index 0000000..54bf0a2 --- /dev/null +++ b/include/ctnp/parsing/Tokens.hpp @@ -0,0 +1,678 @@ +// Copyright Dominic (DNKpp) Koepke 2025 - 2025. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef CTNP_PARSING_TOKENS_HPP +#define CTNP_PARSING_TOKENS_HPP + +#include "ctnp/config/Config.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ctnp::parsing +{ + template + concept parser_visitor = std::movable + && requires(std::unwrap_reference_t visitor, std::string_view content, std::ptrdiff_t count) { + visitor.unrecognized(content); + + visitor.begin(); + visitor.end(); + + visitor.begin_type(); + visitor.end_type(); + + visitor.begin_scope(); + visitor.end_scope(); + + visitor.add_identifier(content); + visitor.add_arg(); + + visitor.begin_template_args(count); + visitor.end_template_args(); + + visitor.add_const(); + visitor.add_volatile(); + visitor.add_noexcept(); + visitor.add_ptr(); + visitor.add_lvalue_ref(); + visitor.add_rvalue_ref(); + + visitor.begin_function(); + visitor.end_function(); + visitor.begin_return_type(); + visitor.end_return_type(); + visitor.begin_function_args(count); + visitor.end_function_args(); + + visitor.begin_function_ptr(); + visitor.end_function_ptr(); + + visitor.begin_operator_identifier(); + visitor.end_operator_identifier(); + }; + + template + [[nodiscard]] + constexpr auto& unwrap_visitor(Visitor& visitor) noexcept + { + return static_cast< + std::add_lvalue_reference_t< + std::unwrap_reference_t>>(visitor); + } +} + +namespace ctnp::parsing::token +{ + class Type; + + class Space + { + }; + + class OperatorKeyword + { + }; + + class ScopeResolution + { + public: + std::string_view content; + }; + + class ArgSeparator + { + public: + std::string_view content; + }; + + class OpeningAngle + { + public: + std::string_view content; + }; + + class ClosingAngle + { + public: + std::string_view content; + }; + + class OpeningParens + { + public: + std::string_view content; + }; + + class ClosingParens + { + public: + std::string_view content; + }; + + class OpeningCurly + { + public: + std::string_view content; + }; + + class ClosingCurly + { + public: + std::string_view content; + }; + + class OpeningBacktick + { + public: + std::string_view content; + }; + + class ClosingSingleQuote + { + public: + std::string_view content; + }; + + class TypeContext + { + public: + std::string_view content; + }; + + class Specs + { + public: + struct Layer + { + bool isConst{false}; + bool isVolatile{false}; + + template + constexpr void operator()(Visitor& visitor) const + { + auto& inner = unwrap_visitor(visitor); + + if (isConst) + { + inner.add_const(); + } + + if (isVolatile) + { + inner.add_volatile(); + } + } + }; + + std::vector layers{1u}; + + enum Refness : std::uint8_t + { + none, + lvalue, + rvalue + }; + + Refness refness{none}; + bool isNoexcept{false}; + + [[nodiscard]] + constexpr bool has_ptr() const noexcept + { + return 1u < layers.size(); + } + + template + constexpr void operator()(Visitor& visitor) const + { + CTNP_ASSERT(!layers.empty(), "Invalid state."); + + auto& unwrapped = unwrap_visitor(visitor); + + std::invoke(layers.front(), unwrapped); + + for (auto const& layer : layers | std::views::drop(1u)) + { + unwrapped.add_ptr(); + std::invoke(layer, unwrapped); + } + + switch (refness) + { + case lvalue: + unwrapped.add_lvalue_ref(); + break; + + case rvalue: + unwrapped.add_rvalue_ref(); + break; + + case none: [[fallthrough]]; + default: break; + } + + if (isNoexcept) + { + unwrapped.add_noexcept(); + } + } + }; + + class ArgSequence + { + public: + std::vector types; + + constexpr ~ArgSequence() noexcept; + constexpr ArgSequence(); + constexpr ArgSequence(ArgSequence const&); + constexpr ArgSequence& operator=(ArgSequence const&); + constexpr ArgSequence(ArgSequence&&) noexcept; + constexpr ArgSequence& operator=(ArgSequence&&) noexcept; + + template + constexpr void operator()(Visitor& visitor) const; + + template + constexpr void handle_as_template_args(Visitor& visitor) const; + }; + + class Identifier + { + public: + bool isBuiltinType{false}; + + struct OperatorInfo + { + using Symbol = std::variant>; + Symbol symbol{}; + }; + + using Content = std::variant; + Content content{}; + std::optional templateArgs{}; + + [[nodiscard]] + constexpr bool is_template() const noexcept + { + return templateArgs.has_value(); + } + + [[nodiscard]] + constexpr bool is_void() const noexcept + { + auto const* const id = std::get_if(&content); + + return id + && "void" == *id; + } + + [[nodiscard]] + constexpr bool is_reserved() const noexcept + { + auto const* const id = std::get_if(&content); + + return id + && id->starts_with("__"); + } + + [[nodiscard]] + constexpr bool is_builtin() const noexcept + { + return isBuiltinType; + } + + template + constexpr void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + std::visit( + [&](auto const& inner) { handle_content(unwrapped, inner); }, + content); + + if (templateArgs) + { + templateArgs->handle_as_template_args(unwrapped); + } + } + + public: + template + static constexpr void handle_content(Visitor& visitor, std::string_view const& content) + { + CTNP_ASSERT(!content.empty(), "Empty identifier is not allowed."); + + visitor.add_identifier(content); + } + + template + static constexpr void handle_content(Visitor& visitor, OperatorInfo const& content) + { + visitor.begin_operator_identifier(); + std::visit( + [&](auto const& symbol) { handle_op_symbol(visitor, symbol); }, + content.symbol); + visitor.end_operator_identifier(); + } + + template + static constexpr void handle_op_symbol(Visitor& visitor, std::string_view const& symbol) + { + CTNP_ASSERT(!symbol.empty(), "Empty symbol is not allowed."); + + visitor.add_identifier(symbol); + } + + template + static constexpr void handle_op_symbol(Visitor& visitor, std::shared_ptr const& type) + { + CTNP_ASSERT(type, "Empty type-symbol is not allowed."); + + std::invoke(*type, visitor); + } + }; + + class FunctionContext + { + public: + ArgSequence args{}; + Specs specs{}; + + template + constexpr void operator()(Visitor& visitor) const; + }; + + class FunctionIdentifier + { + public: + Identifier identifier; + FunctionContext context{}; + + template + constexpr void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + std::invoke(identifier, unwrapped); + std::invoke(context, unwrapped); + } + }; + + class ScopeSequence + { + public: + using Scope = std::variant; + std::vector scopes{}; + + template + constexpr void operator()(Visitor& visitor) const + { + CTNP_ASSERT(!scopes.empty(), "Empty scope-sequence is not allowed."); + + auto& unwrapped = unwrap_visitor(visitor); + + for (auto const& scope : scopes) + { + unwrapped.begin_scope(); + std::visit( + [&](auto const& id) { handle_scope(unwrapped, id); }, + scope); + unwrapped.end_scope(); + } + } + + private: + template + constexpr void handle_scope(Visitor& visitor, Identifier const& scope) const + { + std::invoke(scope, visitor); + } + + template + constexpr void handle_scope(Visitor& visitor, FunctionIdentifier const& scope) const + { + visitor.begin_function(); + std::invoke(scope, visitor); + visitor.end_function(); + } + }; + + class RegularType + { + public: + std::optional scopes{}; + Identifier identifier; + Specs specs{}; + + template + constexpr void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_type(); + + if (scopes) + { + std::invoke(*scopes, unwrapped); + } + + std::invoke(identifier, unwrapped); + std::invoke(specs, unwrapped); + + unwrapped.end_type(); + } + }; + + class FunctionType + { + public: + std::shared_ptr returnType{}; + FunctionContext context{}; + + template + void operator()(Visitor& visitor) const + { + CTNP_ASSERT(returnType, "Return type is mandatory for function-types."); + + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_function(); + + unwrapped.begin_return_type(); + std::invoke(*returnType, visitor); + unwrapped.end_return_type(); + + std::invoke(context, unwrapped); + + unwrapped.end_function(); + } + }; + + class FunctionPtr + { + public: + std::optional scopes{}; + Specs specs{}; + + struct NestedInfo + { + std::shared_ptr ptr{}; + FunctionContext ctx{}; + }; + + std::optional nested{}; + }; + + class FunctionPtrType + { + public: + std::shared_ptr returnType{}; + std::optional scopes{}; + Specs specs{}; + FunctionContext context{}; + + template + constexpr void operator()(Visitor& visitor) const + { + CTNP_ASSERT(returnType, "Return type is mandatory for function-ptrs."); + + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_type(); + + unwrapped.begin_return_type(); + std::invoke(*returnType, visitor); + unwrapped.end_return_type(); + + unwrapped.begin_function_ptr(); + if (scopes) + { + std::invoke(*scopes, unwrapped); + } + + std::invoke(specs, unwrapped); + unwrapped.end_function_ptr(); + + std::invoke(context, unwrapped); + + unwrapped.end_type(); + } + }; + + class Type + { + public: + using State = std::variant; + State state; + + [[nodiscard]] + constexpr bool is_void() const noexcept + { + auto const* const regularType = std::get_if(&state); + + return regularType + && regularType->identifier.is_void(); + } + + [[nodiscard]] + constexpr Specs& specs() noexcept + { + return std::visit( + [&](auto& inner) noexcept -> Specs& { return specs(inner); }, + state); + } + + template + void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + std::visit( + [&](auto const& inner) { std::invoke(inner, unwrapped); }, + state); + } + + private: + [[nodiscard]] + static constexpr Specs& specs(RegularType& type) noexcept + { + return type.specs; + } + + [[nodiscard]] + static constexpr Specs& specs(FunctionType& type) noexcept + { + return type.context.specs; + } + + [[nodiscard]] + static constexpr Specs& specs(FunctionPtrType& type) noexcept + { + return type.specs; + } + }; + + constexpr ArgSequence::~ArgSequence() noexcept = default; + constexpr ArgSequence::ArgSequence() = default; + constexpr ArgSequence::ArgSequence(ArgSequence const&) = default; + constexpr ArgSequence& ArgSequence::operator=(ArgSequence const&) = default; + constexpr ArgSequence::ArgSequence(ArgSequence&&) noexcept = default; + constexpr ArgSequence& ArgSequence::operator=(ArgSequence&&) noexcept = default; + + template + constexpr void ArgSequence::operator()(Visitor& visitor) const + { + if (!types.empty()) + { + auto& unwrapped = unwrap_visitor(visitor); + + std::invoke(types.front(), unwrapped); + + for (auto const& type : types | std::views::drop(1)) + { + unwrapped.add_arg(); + std::invoke(type, unwrapped); + } + } + } + + template + constexpr void ArgSequence::handle_as_template_args(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_template_args(std::ranges::ssize(types)); + std::invoke(*this, unwrapped); + unwrapped.end_template_args(); + } + + template + constexpr void FunctionContext::operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_function_args(std::ranges::ssize(args.types)); + std::invoke(args, unwrapped); + unwrapped.end_function_args(); + std::invoke(specs, unwrapped); + } + + class Function + { + public: + std::shared_ptr returnType{}; + std::optional scopes{}; + FunctionIdentifier identifier{}; + + template + void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_function(); + + if (returnType) + { + unwrapped.begin_return_type(); + std::invoke(*returnType, visitor); + unwrapped.end_return_type(); + } + + if (scopes) + { + std::invoke(*scopes, unwrapped); + } + + std::invoke(identifier, unwrapped); + + unwrapped.end_function(); + } + }; +} + +namespace ctnp::parsing +{ + using Token = std::variant< + token::Space, + token::OperatorKeyword, + token::ScopeResolution, + token::ArgSeparator, + token::OpeningAngle, + token::ClosingAngle, + token::OpeningParens, + token::ClosingParens, + token::OpeningCurly, + token::ClosingCurly, + token::OpeningBacktick, + token::ClosingSingleQuote, + token::TypeContext, + + token::Identifier, + token::FunctionIdentifier, + token::ScopeSequence, + token::ArgSequence, + token::FunctionContext, + token::FunctionPtr, + token::Specs, + token::Type, + token::Function>; + using TokenStack = std::vector; + + template + concept token_type = requires(Token const& token) { + { std::holds_alternative(token) } -> std::convertible_to; + }; +} + +#endif diff --git a/src/Lexer.cpp b/src/Lexer.cpp index bd6e800..c8919a7 100644 --- a/src/Lexer.cpp +++ b/src/Lexer.cpp @@ -1,4 +1,4 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 2025. +// Copyright Dominic (DNKpp) Koepke 2025 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c3b92c8..8d3569c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,6 +15,8 @@ add_executable(${TARGET_NAME} "Version.cpp" ) +add_subdirectory("parsing") + target_include_directories(${TARGET_NAME} PRIVATE "$" diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp index ab1a5cf..0d9116d 100644 --- a/tests/Lexer.cpp +++ b/tests/Lexer.cpp @@ -1,4 +1,4 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 2025. +// Copyright Dominic (DNKpp) Koepke 2025 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) diff --git a/tests/parsing/CMakeLists.txt b/tests/parsing/CMakeLists.txt new file mode 100644 index 0000000..c5bcdfc --- /dev/null +++ b/tests/parsing/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright Dominic (DNKpp) Koepke 2025 - 2025. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# https://www.boost.org/LICENSE_1_0.txt) + +target_sources(${TARGET_NAME} + PRIVATE + "Tokens.cpp" +) diff --git a/tests/parsing/Tokens.cpp b/tests/parsing/Tokens.cpp new file mode 100644 index 0000000..d1e73d7 --- /dev/null +++ b/tests/parsing/Tokens.cpp @@ -0,0 +1,6 @@ +// Copyright Dominic (DNKpp) Koepke 2025 - 2025. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "ctnp/parsing/Tokens.hpp" \ No newline at end of file From 3489630943810c410ced1d0aefdc0a420b12d355 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 17 May 2025 17:26:10 +0200 Subject: [PATCH 2/9] feat: TypeList.hpp --- include/ctnp/TypeList.hpp | 62 +++++++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/TypeList.cpp | 37 +++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 include/ctnp/TypeList.hpp create mode 100644 tests/TypeList.cpp diff --git a/include/ctnp/TypeList.hpp b/include/ctnp/TypeList.hpp new file mode 100644 index 0000000..d1275e1 --- /dev/null +++ b/include/ctnp/TypeList.hpp @@ -0,0 +1,62 @@ +// Copyright Dominic (DNKpp) Koepke 2025 - 2025. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef CTNP_TYPE_LIST_HPP +#define CTNP_TYPE_LIST_HPP + +#pragma once + +#include +#include +#include +#include + +namespace ctnp::util +{ + /** + * \brief A very basic type-list template. + * \tparam Args The types. + */ + template + struct type_list + { + static constexpr std::size_t size = sizeof...(Args); + }; + + namespace detail + { + template + struct type_list_reverse; + + template + struct type_list_reverse> + { + using type = ProcessedList; + }; + + template + struct type_list_reverse, type_list> + : public type_list_reverse, type_list> + { + }; + } + + template + struct type_list_reverse + { + using type = typename detail::type_list_reverse, TypeList>::type; + }; + + template + using type_list_reverse_t = typename type_list_reverse::type; +} + +template +struct std::tuple_size> // NOLINT(*-dcl58-cpp) + : std::integral_constant::size> +{ +}; + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8d3569c..cef295e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,6 +12,7 @@ set(CMAKE_CXX_STANDARD ${CTNP_CXX_STANDARD}) add_executable(${TARGET_NAME} "Algorithm.cpp" "Lexer.cpp" + "TypeList.cpp" "Version.cpp" ) diff --git a/tests/TypeList.cpp b/tests/TypeList.cpp new file mode 100644 index 0000000..49cc14e --- /dev/null +++ b/tests/TypeList.cpp @@ -0,0 +1,37 @@ +// Copyright Dominic (DNKpp) Koepke 2025 - 2025. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "ctnp/TypeList.hpp" + +using namespace ctnp; + +TEMPLATE_TEST_CASE_SIG( + "type_list::size holds the amount of arguments.", + "[utility]", + ((std::size_t expected, typename... Args), expected, Args...), + (0u), + (1u, int), + (2u, int, int), + (2u, float, int), + (3u, int, int, int)) +{ + using type_list_t = util::type_list; + + STATIC_REQUIRE(expected == type_list_t::size); + STATIC_REQUIRE(expected == std::tuple_size_v); +} + +TEMPLATE_TEST_CASE_SIG( + "type_list_reverse reverses the type_list elements.", + "[utility]", + ((auto dummy, typename Expected, typename Input), dummy, Expected, Input), + (std::ignore, util::type_list<>, util::type_list<>), + (std::ignore, util::type_list, util::type_list), + (std::ignore, util::type_list, util::type_list), + (std::ignore, util::type_list, util::type_list)) +{ + STATIC_REQUIRE(std::same_as::type>); + STATIC_REQUIRE(std::same_as>); +} From 908764056b58abd8ec840d62d6fe446072bd202a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 17 May 2025 18:12:25 +0200 Subject: [PATCH 3/9] feat: add parsing/Reductions.hpp --- include/ctnp/parsing/Reductions.hpp | 753 ++++++++++++++++++++++++++++ tests/parsing/CMakeLists.txt | 1 + tests/parsing/Reductions.cpp | 6 + 3 files changed, 760 insertions(+) create mode 100644 include/ctnp/parsing/Reductions.hpp create mode 100644 tests/parsing/Reductions.cpp diff --git a/include/ctnp/parsing/Reductions.hpp b/include/ctnp/parsing/Reductions.hpp new file mode 100644 index 0000000..732782e --- /dev/null +++ b/include/ctnp/parsing/Reductions.hpp @@ -0,0 +1,753 @@ +// Copyright Dominic (DNKpp) Koepke 202% - 2025. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef CTNP_PARSING_REDUCTIONS_HPP +#define CTNP_PARSING_REDUCTIONS_HPP + +#include "ctnp/TypeList.hpp" +#include "ctnp/config/Config.hpp" +#include "ctnp/parsing/Tokens.hpp" + +#include +#include +#include +#include +#include +#include + +namespace ctnp::parsing +{ + namespace detail + { + template + [[nodiscard]] + constexpr bool is_suffix_of( + [[maybe_unused]] util::type_list const types, + std::span const tokenStack) noexcept + { + if (tokenStack.empty() + || !std::holds_alternative(tokenStack.back())) + { + return false; + } + + if constexpr (0u < sizeof...(Others)) + { + return is_suffix_of( + util::type_list{}, + tokenStack.first(tokenStack.size() - 1)); + } + else + { + return true; + } + } + } + + template + constexpr bool is_suffix_of(std::span const tokenStack) noexcept + { + using types = util::type_list; + + return 1u + sizeof...(Others) <= tokenStack.size() + && detail::is_suffix_of(util::type_list_reverse_t{}, tokenStack); + } + + template + [[nodiscard]] + constexpr auto match_suffix(std::span const tokenStack) noexcept + { + if constexpr (0u == sizeof...(Others)) + { + Leading* result{}; + if (is_suffix_of(tokenStack)) + { + result = &std::get(tokenStack.back()); + } + + return result; + } + else + { + std::optional> result{}; + if (is_suffix_of(tokenStack)) + { + auto const suffix = tokenStack.last(1u + sizeof...(Others)); + + result = std::invoke( + [&]([[maybe_unused]] std::index_sequence const) noexcept { + return std::tie( + std::get(suffix[0u]), + std::get(suffix[1u + indices])...); + }, + std::index_sequence_for{}); + } + + return result; + } + } + + constexpr void remove_suffix(std::span& tokenStack, std::size_t const count) noexcept + { + CTNP_ASSERT(count <= tokenStack.size(), "Count exceeds stack size."); + tokenStack = tokenStack.first(tokenStack.size() - count); + } + + constexpr void ignore_space(std::span& tokenStack) noexcept + { + if (is_suffix_of(tokenStack)) + { + remove_suffix(tokenStack, 1u); + } + } + + constexpr void ignore_reserved_identifier(std::span& tokenStack) noexcept + { + if (auto const* const id = match_suffix(tokenStack); + id + && id->is_reserved()) + { + remove_suffix(tokenStack, 1u); + } + } + + namespace token + { + bool try_reduce_as_type(TokenStack& tokenStack); + + inline bool try_reduce_as_scope_sequence(TokenStack& tokenStack) + { + std::span pendingTokens{tokenStack}; + if (!is_suffix_of(pendingTokens)) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + ScopeSequence::Scope scope{}; + if (auto* identifier = match_suffix(pendingTokens)) + { + scope = std::move(*identifier); + } + else if (auto* funIdentifier = match_suffix(pendingTokens)) + { + scope = std::move(*funIdentifier); + } + else + { + return false; + } + + remove_suffix(pendingTokens, 1u); + tokenStack.resize(pendingTokens.size()); + + if (auto* sequence = match_suffix(tokenStack)) + { + sequence->scopes.emplace_back(std::move(scope)); + } + else + { + tokenStack.emplace_back( + ScopeSequence{ + .scopes = {std::move(scope)}}); + } + + return true; + } + + constexpr bool try_reduce_as_arg_sequence(TokenStack& tokenStack) + { + std::span pendingTokens{tokenStack}; + if (std::optional suffix = match_suffix(pendingTokens)) + { + // Keep ArgSequence + remove_suffix(pendingTokens, 2u); + auto& [seq, sep, type] = *suffix; + + seq.types.emplace_back(std::move(type)); + tokenStack.resize(pendingTokens.size()); + + return true; + } + + if (auto* type = match_suffix(pendingTokens)) + { + remove_suffix(pendingTokens, 1u); + + ArgSequence seq{}; + seq.types.emplace_back(std::move(*type)); + tokenStack.resize(pendingTokens.size()); + tokenStack.emplace_back(std::move(seq)); + + return true; + } + + return false; + } + + constexpr bool try_reduce_as_template_identifier(TokenStack& tokenStack) + { + std::span pendingTokens{tokenStack}; + if (!is_suffix_of(pendingTokens)) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + auto* args = match_suffix(pendingTokens); + if (args) + { + remove_suffix(pendingTokens, 1u); + } + + if (!is_suffix_of(pendingTokens)) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + auto* id = match_suffix(pendingTokens); + if (!id + || id->is_template()) + { + return false; + } + + if (args) + { + id->templateArgs = std::move(*args); + } + else + { + id->templateArgs.emplace(); + } + tokenStack.resize(pendingTokens.size()); + + return true; + } + + constexpr bool try_reduce_as_function_context(TokenStack& tokenStack) + { + std::span pendingTokens{tokenStack}; + if (!is_suffix_of(pendingTokens)) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + auto* args = match_suffix(pendingTokens); + if (args) + { + remove_suffix(pendingTokens, 1u); + } + + if (!is_suffix_of(pendingTokens)) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + // There can never be valid function-args in form of `::()`, thus reject it. + if (is_suffix_of(pendingTokens) + || is_suffix_of(pendingTokens)) + { + return false; + } + + FunctionContext funCtx{}; + if (args) + { + // We omit function args with only `void`. + if (1u != args->types.size() + || !args->types.front().is_void()) + { + funCtx.args = std::move(*args); + } + } + + tokenStack.resize(pendingTokens.size()); + tokenStack.emplace_back(std::move(funCtx)); + + return true; + } + + inline bool try_reduce_as_function_identifier(TokenStack& tokenStack) + { + std::span pendingStack{tokenStack}; + + // There may be a space, when the function is wrapped inside single-quotes. + ignore_space(pendingStack); + + // Ignore something like `__ptr64` on msvc. + ignore_reserved_identifier(pendingStack); + + if (std::optional suffix = match_suffix(pendingStack)) + { + remove_suffix(pendingStack, 2u); + + auto& [identifier, funCtx] = *suffix; + FunctionIdentifier funIdentifier{ + .identifier = std::move(identifier), + .context = std::move(funCtx)}; + + tokenStack.resize(pendingStack.size() + 1u); + tokenStack.back() = std::move(funIdentifier); + + return true; + } + + return false; + } + + [[nodiscard]] + constexpr bool is_identifier_prefix(std::span const tokenStack) noexcept + { + return tokenStack.empty() + || is_suffix_of(tokenStack) + || is_suffix_of(tokenStack) + || is_suffix_of(tokenStack) + || is_suffix_of(tokenStack) + || is_suffix_of(tokenStack) + || is_suffix_of(tokenStack) + || is_suffix_of(tokenStack) + || is_suffix_of(tokenStack); + } + + template + constexpr bool try_reduce_as_placeholder_identifier_wrapped(TokenStack& tokenStack) + { + CTNP_ASSERT(is_suffix_of(tokenStack), "Token-stack does not have the closing token as top.", tokenStack); + std::span pendingTokens{tokenStack.begin(), tokenStack.end() - 1}; + + auto const openingIter = std::ranges::find_if( + pendingTokens | std::views::reverse, + [](Token const& token) noexcept { return std::holds_alternative(token); }); + if (openingIter == pendingTokens.rend() + || !is_identifier_prefix({pendingTokens.begin(), openingIter.base() - 1})) + { + return false; + } + + // Just treat everything between the opening and closing as placeholder identifier. + auto const& opening = std::get(*std::ranges::prev(openingIter.base(), 1)); + auto const& closing = std::get(tokenStack.back()); + auto const contentLength = (closing.content.data() - opening.content.data()) + closing.content.size(); + std::string_view const content{opening.content.data(), contentLength}; + + pendingTokens = std::span{pendingTokens.begin(), openingIter.base() - 1}; + + // There may be a space in front of the placeholder, which isn't necessary. + ignore_space(pendingTokens); + + tokenStack.resize(pendingTokens.size() + 1u); + tokenStack.back() = Identifier{.content = content}; + + return true; + } + + inline bool try_reduce_as_function_type(TokenStack& tokenStack) + { + std::span pendingTokens{tokenStack}; + + auto* const ctx = match_suffix(pendingTokens); + if (!ctx) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + // The return type is always delimited by space from the arg-list. + if (!is_suffix_of(pendingTokens)) + { + // Well, of course there is an exception to the "always". + // There is that case on msvc, where it does not add that space between the function-args and the call-convention. + // E.g. `void __cdecl()`. + // But, as we can be pretty sure from the context, that the identifier can never be the function name, accept it + // as valid delimiter. + if (auto const* const id = match_suffix(pendingTokens); + !id + || !id->is_reserved()) + { + return false; + } + } + remove_suffix(pendingTokens, 1u); + + // Ignore call-convention. + ignore_reserved_identifier(pendingTokens); + + auto* const returnType = match_suffix(pendingTokens); + if (!returnType) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + FunctionType funType{ + .returnType = std::make_shared(std::move(*returnType)), + .context = std::move(*ctx)}; + + tokenStack.resize( + std::exchange(pendingTokens, {}).size() + 1u); + tokenStack.back().emplace(std::move(funType)); + + return true; + } + + inline bool try_reduce_as_function_ptr(TokenStack& tokenStack) + { + std::span pendingTokens{tokenStack}; + if (!is_suffix_of(pendingTokens)) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + auto* nestedFunCtx = match_suffix(pendingTokens); + FunctionPtr* nestedFunPtr{}; + if (nestedFunCtx) + { + remove_suffix(pendingTokens, 1u); + + if (auto* ptr = match_suffix(pendingTokens)) + { + nestedFunPtr = ptr; + remove_suffix(pendingTokens, 1u); + } + } + + ignore_space(pendingTokens); + // Ignore call-convention. + ignore_reserved_identifier(pendingTokens); + + auto* specs = match_suffix(pendingTokens); + ScopeSequence* scopeSeq{}; + if (specs && specs->has_ptr()) + { + remove_suffix(pendingTokens, 1u); + + if (auto* const seq = match_suffix(pendingTokens)) + { + scopeSeq = seq; + remove_suffix(pendingTokens, 1u); + } + + // Ignore call-convention, which may have already been reduced to a type. + if (auto const* const type = match_suffix(pendingTokens)) + { + if (auto const* const regular = std::get_if(&type->state); + regular + && regular->identifier.is_reserved()) + { + remove_suffix(pendingTokens, 1u); + } + } + } + else + { + RegularType* regular{}; + if (auto* const type = match_suffix(pendingTokens)) + { + regular = std::get_if(&type->state); + } + + // Unfortunately msvc produces something like `(__cdecl*)` for the function-ptr part. + // There is no way to reliably detect whether denotes a function-ptr or argument-list. + // So we have to make sure, that the reduction is only called in the right places. + // Then we can extract the info from that type. + if (!regular + || !regular->identifier.is_reserved() + || !regular->specs.has_ptr()) + { + return false; + } + + specs = ®ular->specs; + remove_suffix(pendingTokens, 1u); + } + + if (!is_suffix_of(pendingTokens)) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + FunctionPtr funPtr{.specs = std::move(*specs)}; + if (scopeSeq) + { + funPtr.scopes = std::move(*scopeSeq); + } + + if (nestedFunCtx) + { + FunctionPtr::NestedInfo nested{ + .ctx = std::move(*nestedFunCtx)}; + + if (nestedFunPtr) + { + nested.ptr = std::make_shared(std::move(*nestedFunPtr)); + } + + funPtr.nested = std::move(nested); + } + + tokenStack.resize(pendingTokens.size()); + tokenStack.emplace_back(std::move(funPtr)); + + return true; + } + + namespace detail + { + void handled_nested_function_ptr(TokenStack& tokenStack, FunctionPtr::NestedInfo info); + } + + inline bool try_reduce_as_function_ptr_type(TokenStack& tokenStack) + { + std::span pendingTokens{tokenStack}; + + // Ignore something like `__ptr64`. + ignore_reserved_identifier(pendingTokens); + + // The return type is always delimited by space from the spec part. + if (std::optional suffix = match_suffix(pendingTokens)) + { + remove_suffix(pendingTokens, 4u); + auto& [returnType, space, ptr, ctx] = *suffix; + + std::optional nestedInfo = std::move(ptr.nested); + FunctionPtrType ptrType{ + .returnType = std::make_shared(std::move(returnType)), + .scopes = std::move(ptr.scopes), + .specs = std::move(ptr.specs), + .context = std::move(ctx)}; + + tokenStack.resize( + std::exchange(pendingTokens, {}).size() + 1u); + tokenStack.back().emplace(std::move(ptrType)); + + // We got something like `ret (*(outer-args))(args)` or `ret (*(*)(outer-args))(args)`, where the currently + // processed function-ptr is actually the return-type of the inner function(-ptr). + // This may nested in an arbitrary depth! + if (nestedInfo) + { + detail::handled_nested_function_ptr(tokenStack, *std::move(nestedInfo)); + } + + return true; + } + + return false; + } + + namespace detail + { + inline void handled_nested_function_ptr(TokenStack& tokenStack, FunctionPtr::NestedInfo info) + { + auto& [ptr, ctx] = info; + + // We need to insert an extra space, to follow the general syntax constraints. + tokenStack.emplace_back(Space{}); + + bool const isFunPtr{ptr}; + if (ptr) + { + tokenStack.emplace_back(std::move(*ptr)); + } + + tokenStack.emplace_back(std::move(ctx)); + + if (isFunPtr) + { + try_reduce_as_function_ptr_type(tokenStack); + } + else + { + try_reduce_as_function_type(tokenStack); + } + } + } + + inline bool try_reduce_as_regular_type(TokenStack& tokenStack) + { + std::span pendingTokens{tokenStack}; + auto* const identifier = match_suffix(pendingTokens); + if (!identifier) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + // There may be the case, where we already reduced a Type but additionally got something like `__ptr64`. + // E.g. `int& __ptr64`. Remove that trailing identifier and treat it as a successful reduction. + if (identifier->is_reserved() + && is_suffix_of(pendingTokens)) + { + tokenStack.pop_back(); + + return true; + } + + auto* const scopes = match_suffix(pendingTokens); + if (scopes) + { + remove_suffix(pendingTokens, 1u); + } + + auto* const prefixSpecs = match_suffix(pendingTokens); + if (prefixSpecs) + { + // Prefix-specs can only have `const` and/or `volatile`. + if (auto const& layers = prefixSpecs->layers; + token::Specs::Refness::none != prefixSpecs->refness + || prefixSpecs->isNoexcept + || 1u != layers.size()) + { + return false; + } + + remove_suffix(pendingTokens, 1u); + } + + // We do never allow two or more adjacent `Type` tokens, as there is literally no case where this would make sense. + if (is_suffix_of(pendingTokens) + || is_suffix_of(pendingTokens)) + { + return false; + } + + RegularType newType{.identifier = std::move(*identifier)}; + if (prefixSpecs) + { + newType.specs = std::move(*prefixSpecs); + } + + if (scopes) + { + newType.scopes = std::move(*scopes); + } + + // Ignore something like `class` or `struct` directly in front of a type. + if (is_suffix_of(pendingTokens)) + { + remove_suffix(pendingTokens, 1u); + } + + tokenStack.resize(pendingTokens.size()); + tokenStack.emplace_back( + std::in_place_type, + std::move(newType)); + + return true; + } + + inline bool try_reduce_as_type(TokenStack& tokenStack) + { + return try_reduce_as_function_ptr_type(tokenStack) + || try_reduce_as_function_type(tokenStack) + || try_reduce_as_regular_type(tokenStack); + } + + inline bool try_reduce_as_function(TokenStack& tokenStack) + { + std::span pendingTokens{tokenStack}; + if (auto* funIdentifier = match_suffix(pendingTokens)) + { + Function function{ + .identifier = std::move(*funIdentifier)}; + remove_suffix(pendingTokens, 1u); + + if (auto* scopes = match_suffix(pendingTokens)) + { + function.scopes = std::move(*scopes); + remove_suffix(pendingTokens, 1u); + } + + // Ignore call-convention. + ignore_reserved_identifier(pendingTokens); + + if (auto* returnType = match_suffix(pendingTokens)) + { + function.returnType = std::make_shared(std::move(*returnType)); + remove_suffix(pendingTokens, 1u); + } + + tokenStack.resize( + std::exchange(pendingTokens, {}).size()); + tokenStack.emplace_back(std::move(function)); + + return true; + } + + return false; + } + + inline void reduce_as_conversion_operator_function_identifier(TokenStack& tokenStack) + { + // Functions reported by stacktrace are sometimes not in actual function form, + // so we need to be more permissive here. + std::optional funCtx{}; + if (auto* const ctx = match_suffix(tokenStack)) + { + funCtx = std::move(*ctx); + tokenStack.pop_back(); + } + + try_reduce_as_type(tokenStack); + CTNP_ASSERT(is_suffix_of(tokenStack), "Invalid state", tokenStack); + auto targetType = std::make_shared( + std::get(std::move(tokenStack.back()))); + tokenStack.pop_back(); + + CTNP_ASSERT(is_suffix_of(tokenStack), "Invalid state", tokenStack); + tokenStack.back() = Identifier{ + .content = Identifier::OperatorInfo{.symbol = std::move(targetType)}}; + + if (funCtx) + { + tokenStack.emplace_back(*std::move(funCtx)); + try_reduce_as_function_identifier(tokenStack); + } + } + + [[nodiscard]] + constexpr Specs& get_or_emplace_specs(TokenStack& tokenStack) + { + // Maybe wo got something like `type&` and need to reduce that identifier to an actual `Type` token. + if (is_suffix_of(tokenStack)) + { + if (try_reduce_as_type(tokenStack)) + { + return std::get(tokenStack.back()).specs(); + } + + // The reduction failed, so it's something like `__ptr64` and should be ignored. + // This may happen, when specs are attached to functions, like `ret T::foo() & __ptr64`. + CTNP_ASSERT(std::get(tokenStack.back()).is_reserved(), "Unexpected token.", tokenStack); + tokenStack.pop_back(); + } + + if (auto* const type = match_suffix(tokenStack)) + { + return type->specs(); + } + + if (auto* const ctx = match_suffix(tokenStack)) + { + return ctx->specs; + } + + if (auto* specs = match_suffix(tokenStack)) + { + return *specs; + } + + // No specs found yet? Assume prefix specs. + return std::get(tokenStack.emplace_back(Specs{})); + } + } +} + +#endif diff --git a/tests/parsing/CMakeLists.txt b/tests/parsing/CMakeLists.txt index c5bcdfc..0abbb11 100644 --- a/tests/parsing/CMakeLists.txt +++ b/tests/parsing/CMakeLists.txt @@ -5,5 +5,6 @@ target_sources(${TARGET_NAME} PRIVATE + "Reductions.cpp" "Tokens.cpp" ) diff --git a/tests/parsing/Reductions.cpp b/tests/parsing/Reductions.cpp new file mode 100644 index 0000000..a8b843d --- /dev/null +++ b/tests/parsing/Reductions.cpp @@ -0,0 +1,6 @@ +// Copyright Dominic (DNKpp) Koepke 2025 - 2025. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "ctnp/parsing/Reductions.hpp" \ No newline at end of file From 75670f89460d8d4852219e943d9d08ada395ec78 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 17 May 2025 18:27:26 +0200 Subject: [PATCH 4/9] feat: util::contains algorithm --- include/ctnp/Algorithm.hpp | 50 ++++++++++++++++++++++++++++++++++++++ tests/Algorithm.cpp | 33 +++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/include/ctnp/Algorithm.hpp b/include/ctnp/Algorithm.hpp index c4481c6..f84a34a 100644 --- a/include/ctnp/Algorithm.hpp +++ b/include/ctnp/Algorithm.hpp @@ -164,6 +164,56 @@ namespace ctnp::util return {lower, end}; } + + namespace detail + { + struct contains_fn + { + template < + std::input_iterator Iterator, + std::sentinel_for Sentinel, + typename Projection = std::identity, + typename T = util::projected_value_t> + requires std::indirect_binary_predicate< + std::ranges::equal_to, + std::projected, + T const*> + [[nodiscard]] + constexpr bool operator()(Iterator first, Sentinel last, T const& value, Projection projection = {}) const + { + auto const iter = std::ranges::find(std::move(first), last, value, std::move(projection)); + return iter != last; + } + + template < + std::ranges::input_range Range, + typename Projection = std::identity, + typename T = util::projected_value_t, Projection>> + requires std::indirect_binary_predicate< + std::ranges::equal_to, + std::projected, Projection>, + T const*> + [[nodiscard]] + constexpr bool operator()(Range&& r, T const& value, Projection projection = {}) const + { + return std::invoke( + *this, + std::ranges::begin(r), + std::ranges::end(r), + value, + std::move(projection)); + } + }; + } + + /** + * \brief Determines, whether the specified value is contained in the given range. + * \return Returns `true`, when the value is contained. + * + * \note This is a backport from c++23 `std::ranges::contains`. + * \see https://en.cppreference.com/w/cpp/algorithm/ranges/contains + */ + inline constexpr detail::contains_fn contains{}; } #endif diff --git a/tests/Algorithm.cpp b/tests/Algorithm.cpp index 7da6206..3806811 100644 --- a/tests/Algorithm.cpp +++ b/tests/Algorithm.cpp @@ -177,3 +177,36 @@ TEST_CASE( Catch::Matchers::RangeEquals(std::vector{"foo", "foo-bar", "foofoo"})); } } + + +TEST_CASE( + "util::contains determines, whether the specified value is contained.", + "[util][util::algorithm]") +{ + SECTION("When container is empty.") + { + std::vector const collection{}; + + CHECK(!util::contains(collection, 42)); + } + + SECTION("When container contains just a single element.") + { + std::vector const collection = {42}; + + CHECK(util::contains(collection, 42)); + CHECK(!util::contains(collection, 41)); + } + + SECTION("When container contains multiple elements.") + { + std::vector const collection = {42, 1337, 1338}; + + CHECK(util::contains(collection, 42)); + CHECK(util::contains(collection, 1337)); + CHECK(util::contains(collection, 1338)); + + CHECK(!util::contains(collection, 41)); + CHECK(!util::contains(collection, 1339)); + } +} From 0a1323b0df7f1e205e5972e371e7acd69ce749bd Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 17 May 2025 21:17:10 +0200 Subject: [PATCH 5/9] feat: add parsing::Parser --- include/ctnp/parsing/Parser.hpp | 133 ++ src/CMakeLists.txt | 2 + src/parsing/CMakeLists.txt | 9 + src/parsing/Parser.cpp | 573 +++++++ tests/parsing/CMakeLists.txt | 1 + tests/parsing/Parser.cpp | 2619 +++++++++++++++++++++++++++++++ 6 files changed, 3337 insertions(+) create mode 100644 include/ctnp/parsing/Parser.hpp create mode 100644 src/parsing/CMakeLists.txt create mode 100644 src/parsing/Parser.cpp create mode 100644 tests/parsing/Parser.cpp diff --git a/include/ctnp/parsing/Parser.hpp b/include/ctnp/parsing/Parser.hpp new file mode 100644 index 0000000..fa9fc2c --- /dev/null +++ b/include/ctnp/parsing/Parser.hpp @@ -0,0 +1,133 @@ +// Copyright Dominic (DNKpp) Koepke 2025 - 2025. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef CTNP_PARSING_PARSER_HPP +#define CTNP_PARSING_PARSER_HPP + +#pragma once + +#include "ctnp/Lexer.hpp" +#include "ctnp/parsing/Tokens.hpp" + +#include +#include +#include +#include + +namespace ctnp::parsing::detail +{ + using TypeResult = std::optional; + using FunctionResult = std::variant; + + class ParserImpl + { + public: + [[nodiscard]] + explicit ParserImpl(std::string_view const& content) noexcept; + + [[nodiscard]] + constexpr std::string_view content() const noexcept + { + return m_Content; + } + + [[nodiscard]] + TypeResult parse_type(); + + [[nodiscard]] + FunctionResult parse_function(); + + private: + std::string_view m_Content; + lexing::Lexer m_Lexer; + bool m_HasConversionOperator{false}; + + std::vector m_TokenStack{}; + + template + [[nodiscard]] + constexpr LexerTokenClass const* peek_if() const noexcept + { + return std::get_if(&m_Lexer.peek().classification); + } + + void parse(); + + [[nodiscard]] + bool merge_with_next_token() const noexcept; + [[nodiscard]] + bool process_simple_operator(); + void unwrap_msvc_like_function(); + + static void handle_lexer_token(std::string_view content, lexing::token::End const& end); + void handle_lexer_token(std::string_view content, lexing::token::Space const& space); + void handle_lexer_token(std::string_view content, lexing::token::Identifier const& identifier); + void handle_lexer_token(std::string_view content, lexing::token::Keyword const& keyword); + void handle_lexer_token(std::string_view content, lexing::token::OperatorOrPunctuator const& token); + }; + + template + struct ResultVisitor + { + Visitor& visitor; + std::string_view content; + + constexpr void operator()([[maybe_unused]] std::monostate const) const + { + visitor.unrecognized(content); + } + + constexpr void operator()(auto const& result) const + { + visitor.begin(); + std::invoke(result, visitor); + visitor.end(); + } + }; +} + +namespace ctnp::parsing +{ + template + class Parser + { + public: + [[nodiscard]] + explicit constexpr Parser(Visitor visitor, std::string_view content) noexcept(std::is_nothrow_move_constructible_v) + : m_Visitor{std::move(visitor)}, + m_Parser{std::move(content)} + { + } + + void parse_type() + { + auto& unwrapped = unwrap_visitor(m_Visitor); + if (std::optional const result = m_Parser.parse_type()) + { + unwrapped.begin(); + std::invoke(*result, m_Visitor); + unwrapped.end(); + } + else + { + unwrapped.unrecognized(m_Parser.content()); + } + } + + void parse_function() + { + detail::ResultVisitor visitor{ + .visitor = unwrap_visitor(m_Visitor), + .content = m_Parser.content()}; + std::visit(visitor, m_Parser.parse_function()); + } + + private: + Visitor m_Visitor; + detail::ParserImpl m_Parser; + }; +} + +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c0ddeac..86dfb50 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,3 +7,5 @@ target_sources(${TARGET_NAME} PRIVATE "Lexer.cpp" ) + +add_subdirectory("parsing") diff --git a/src/parsing/CMakeLists.txt b/src/parsing/CMakeLists.txt new file mode 100644 index 0000000..2ae9440 --- /dev/null +++ b/src/parsing/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright Dominic (DNKpp) Koepke 2025 - 2025. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# https://www.boost.org/LICENSE_1_0.txt) + +target_sources(${TARGET_NAME} + PRIVATE + "Parser.cpp" +) diff --git a/src/parsing/Parser.cpp b/src/parsing/Parser.cpp new file mode 100644 index 0000000..65e00d6 --- /dev/null +++ b/src/parsing/Parser.cpp @@ -0,0 +1,573 @@ +// Copyright Dominic (DNKpp) Koepke 2025 - 2025. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "ctnp/parsing/Parser.hpp" +#include "ctnp/Lexer.hpp" +#include "ctnp/config/Config.hpp" +#include "ctnp/parsing/Reductions.hpp" +#include "ctnp/parsing/Tokens.hpp" + +#include +#include +#include +#include +#include +#include + +namespace ctnp::parsing::detail +{ + using parsing::is_suffix_of; + + namespace + { + constexpr lexing::token::OperatorOrPunctuator openingParens{"("}; + constexpr lexing::token::OperatorOrPunctuator closingParens{")"}; + constexpr lexing::token::OperatorOrPunctuator openingAngle{"<"}; + constexpr lexing::token::OperatorOrPunctuator closingAngle{">"}; + constexpr lexing::token::OperatorOrPunctuator openingCurly{"{"}; + constexpr lexing::token::OperatorOrPunctuator closingCurly{"}"}; + constexpr lexing::token::OperatorOrPunctuator openingSquare{"["}; + constexpr lexing::token::OperatorOrPunctuator closingSquare{"]"}; + constexpr lexing::token::OperatorOrPunctuator backtick{"`"}; + constexpr lexing::token::OperatorOrPunctuator singleQuote{"'"}; + constexpr lexing::token::OperatorOrPunctuator scopeResolution{"::"}; + constexpr lexing::token::OperatorOrPunctuator commaSeparator{","}; + constexpr lexing::token::OperatorOrPunctuator pointer{"*"}; + constexpr lexing::token::OperatorOrPunctuator lvalueRef{"&"}; + constexpr lexing::token::OperatorOrPunctuator rvalueRef{"&&"}; + constexpr lexing::token::OperatorOrPunctuator colon{":"}; + constexpr lexing::token::OperatorOrPunctuator leftShift{"<<"}; + constexpr lexing::token::OperatorOrPunctuator rightShift{">>"}; + constexpr lexing::token::OperatorOrPunctuator plus{"+"}; + constexpr lexing::token::OperatorOrPunctuator exclamationMark{"!"}; + constexpr lexing::token::Keyword operatorKeyword{"operator"}; + constexpr lexing::token::Keyword constKeyword{"const"}; + constexpr lexing::token::Keyword volatileKeyword{"volatile"}; + constexpr lexing::token::Keyword noexceptKeyword{"noexcept"}; + constexpr lexing::token::Keyword coAwaitKeyword{"co_await"}; + constexpr lexing::token::Keyword newKeyword{"new"}; + constexpr lexing::token::Keyword deleteKeyword{"delete"}; + constexpr lexing::token::Keyword classKeyword{"class"}; + constexpr lexing::token::Keyword structKeyword{"struct"}; + constexpr lexing::token::Keyword enumKeyword{"enum"}; + + constexpr std::array typeKeywordCollection = { + lexing::token::Keyword{"auto"}, + lexing::token::Keyword{"void"}, + lexing::token::Keyword{"bool"}, + lexing::token::Keyword{"char"}, + lexing::token::Keyword{"char8_t"}, + lexing::token::Keyword{"char16_t"}, + lexing::token::Keyword{"char32_t"}, + lexing::token::Keyword{"wchar_t"}, + lexing::token::Keyword{"double"}, + lexing::token::Keyword{"float"}, + lexing::token::Keyword{"int"}, + lexing::token::Keyword{"__int64"}, + lexing::token::Keyword{"long"}, + lexing::token::Keyword{"short"}, + lexing::token::Keyword{"signed"}, + lexing::token::Keyword{"unsigned"}}; + } + + ParserImpl::ParserImpl(std::string_view const& content) noexcept + : m_Content{content}, + m_Lexer{content} + { + } + + TypeResult ParserImpl::parse_type() + { + parse(); + token::try_reduce_as_type(m_TokenStack); + + TypeResult result{}; + if (auto* const end = match_suffix(m_TokenStack); + end + && 1u == m_TokenStack.size()) + { + result = std::move(*end); + m_TokenStack.clear(); + } + + return result; + } + + FunctionResult ParserImpl::parse_function() + { + parse(); + + if (m_HasConversionOperator) + { + token::reduce_as_conversion_operator_function_identifier(m_TokenStack); + } + else + { + is_suffix_of(m_TokenStack) + || token::try_reduce_as_function_identifier(m_TokenStack); + } + + FunctionResult result{}; + token::try_reduce_as_function(m_TokenStack); + if (auto* const function = match_suffix(m_TokenStack); + function + && 1u == m_TokenStack.size()) + { + result = std::move(*function); + m_TokenStack.clear(); + } + else + { + // Well, this is a workaround to circumvent issues with lambdas on some environments. + // gcc produces lambdas in form `` which are not recognized as actual functions. + token::try_reduce_as_type(m_TokenStack); + if (auto* const type = match_suffix(m_TokenStack); + type + && 1u == m_TokenStack.size()) + { + result = std::move(*type); + m_TokenStack.clear(); + } + } + + return result; + } + + void ParserImpl::parse() + { + for (lexing::Token next = m_Lexer.next(); + !std::holds_alternative(next.classification); + next = m_Lexer.next()) + { + std::visit( + [&](auto const& tokenClass) { handle_lexer_token(next.content, tokenClass); }, + next.classification); + } + } + bool ParserImpl::merge_with_next_token() const noexcept + { + auto const* const keyword = peek_if(); + + return keyword + && util::contains(typeKeywordCollection, *keyword); + } + + bool ParserImpl::process_simple_operator() + { + auto dropSpaceInput = [this] { + if (std::holds_alternative(m_Lexer.peek().classification)) + { + std::ignore = m_Lexer.next(); + } + }; + + dropSpaceInput(); + + // As we assume valid input, we do not have to check for the actual symbol. + if (auto const next = m_Lexer.peek(); + auto const* operatorToken = std::get_if(&next.classification)) + { + std::ignore = m_Lexer.next(); + + auto const finishMultiOpOperator = [&, this]([[maybe_unused]] lexing::token::OperatorOrPunctuator const& expectedClosingOp) { + auto const [closingContent, classification] = m_Lexer.next(); + CTNP_ASSERT(lexing::token::TokenClass{expectedClosingOp} == classification, "Invalid input."); + + std::string_view const content{ + next.content.data(), + next.content.size() + closingContent.size()}; + m_TokenStack.emplace_back( + token::Identifier{ + .content = token::Identifier::OperatorInfo{.symbol = content}}); + }; + + if (openingParens == *operatorToken) + { + finishMultiOpOperator(closingParens); + } + else if (openingSquare == *operatorToken) + { + finishMultiOpOperator(closingSquare); + } + // `operator <` and `operator <<` needs to be handled carefully, as it may come in as a template: + // `operator<<>` is actually `operator< <>`. + // Note: No tested c++ compiler actually allows `operator<<>`, but some environments still procude this. + else if (leftShift == *operatorToken) + { + dropSpaceInput(); + + if (auto const* const nextOp = peek_if(); + nextOp + // When next token starts a function or template, we know it's actually `operator <<`. + && (openingParens == *nextOp || openingAngle == *nextOp)) + { + m_TokenStack.emplace_back( + token::Identifier{ + .content = token::Identifier::OperatorInfo{.symbol = next.content}}); + } + // looks like an `operator< <>`, so just treat both `<` separately. + else + { + m_TokenStack.emplace_back( + token::Identifier{ + .content = token::Identifier::OperatorInfo{.symbol = next.content.substr(0u, 1u)}}); + handle_lexer_token(next.content.substr(1u, 1u), openingAngle); + } + } + else + { + m_TokenStack.emplace_back( + token::Identifier{ + .content = token::Identifier::OperatorInfo{.symbol = next.content}}); + } + + dropSpaceInput(); + + return true; + } + else if (auto const* keywordToken = std::get_if(&next.classification); + keywordToken + && util::contains(std::array{newKeyword, deleteKeyword, coAwaitKeyword}, *keywordToken)) + { + std::ignore = m_Lexer.next(); + + std::string_view content = next.content; + + if (newKeyword == *keywordToken || deleteKeyword == *keywordToken) + { + dropSpaceInput(); + + if (auto const* const opAfter = peek_if(); + opAfter + && openingSquare == *opAfter) + { + // Strip `[]` or `[ ]` from the input. + std::ignore = m_Lexer.next(); + dropSpaceInput(); + auto const closing = m_Lexer.next(); + CTNP_ASSERT(closingSquare == std::get(closing.classification), "Invalid input."); + + content = std::string_view{ + next.content.data(), + closing.content.data() + closing.content.size()}; + } + } + + m_TokenStack.emplace_back( + token::Identifier{ + .content = token::Identifier::OperatorInfo{.symbol = content}}); + + dropSpaceInput(); + + return true; + } + + return false; + } + + void ParserImpl::unwrap_msvc_like_function() + { + CTNP_ASSERT(is_suffix_of(m_TokenStack), "Invalid state.", m_TokenStack); + + auto funIdentifier = std::get(m_TokenStack.back()); + m_TokenStack.pop_back(); + + std::optional scopes{}; + if (auto* const scopeSeq = match_suffix(m_TokenStack)) + { + scopes = std::move(*scopeSeq); + m_TokenStack.pop_back(); + } + + // Ignore return-types. + if (is_suffix_of(m_TokenStack)) + { + m_TokenStack.pop_back(); + } + + CTNP_ASSERT(match_suffix(m_TokenStack), "Invalid state.", m_TokenStack); + m_TokenStack.pop_back(); + + // As we gather spaces in front of backticks, there may be a space here, too. + if (is_suffix_of(m_TokenStack)) + { + m_TokenStack.pop_back(); + } + + CTNP_ASSERT(!is_suffix_of(m_TokenStack), "Invalid state.", m_TokenStack); + if (scopes) + { + m_TokenStack.emplace_back(*std::move(scopes)); + } + + m_TokenStack.emplace_back(std::move(funIdentifier)); + } + + void ParserImpl::handle_lexer_token([[maybe_unused]] std::string_view const content, [[maybe_unused]] lexing::token::End const& end) + { + // util::unreachable(); + } + + void ParserImpl::handle_lexer_token(std::string_view const content, [[maybe_unused]] lexing::token::Space const& space) + { + if (auto* const id = match_suffix(m_TokenStack)) + { + // See, whether we need to merge the current builtin identifier with another one. + // E.g. `long long` or `unsigned int`. + if (id->is_builtin() + && merge_with_next_token()) + { + auto& curContent = std::get(id->content); + auto const [nextContent, _] = m_Lexer.next(); + // Merge both keywords by simply treating them as contiguous content. + CTNP_ASSERT(curContent.data() + curContent.size() == content.data(), "Violated expectation."); + CTNP_ASSERT(content.data() + content.size() == nextContent.data(), "Violated expectation."); + curContent = std::string_view{ + curContent.data(), + nextContent.data() + nextContent.size()}; + + return; + } + + token::try_reduce_as_type(m_TokenStack); + } + + // In certain cases, a space after an identifier has semantic significance. + // For example, consider the type names `void ()` and `foo()`: + // - `void ()` represents a function type returning `void`. + // - `foo()` represents a function named `foo`. + if (auto const* const nextOp = peek_if(); + nextOp + && util::contains(std::array{openingAngle, openingParens, openingCurly, singleQuote, backtick}, *nextOp)) + { + m_TokenStack.emplace_back(token::Space{}); + } + } + + void ParserImpl::handle_lexer_token([[maybe_unused]] std::string_view const content, lexing::token::Identifier const& identifier) + { + m_TokenStack.emplace_back( + token::Identifier{.content = identifier.content}); + } + + void ParserImpl::handle_lexer_token(std::string_view const content, lexing::token::Keyword const& keyword) + { + if (constKeyword == keyword) + { + auto& specs = token::get_or_emplace_specs(m_TokenStack); + CTNP_ASSERT(!specs.layers.empty(), "Zero spec layers detected."); + auto& top = specs.layers.back(); + CTNP_ASSERT(!top.isConst, "Specs is already const."); + top.isConst = true; + } + else if (volatileKeyword == keyword) + { + auto& specs = token::get_or_emplace_specs(m_TokenStack); + CTNP_ASSERT(!specs.layers.empty(), "Zero spec layers detected."); + auto& top = specs.layers.back(); + CTNP_ASSERT(!top.isVolatile, "Specs is already volatile."); + top.isVolatile = true; + } + else if (noexceptKeyword == keyword) + { + auto& specs = token::get_or_emplace_specs(m_TokenStack); + CTNP_ASSERT(!specs.isNoexcept, "Specs already is a noexcept."); + specs.isNoexcept = true; + } + else if (operatorKeyword == keyword && !process_simple_operator()) + { + // Conversion operators can not be part of a scope, thus they can not appear multiple times in a single type-name. + CTNP_ASSERT(!m_HasConversionOperator, "Multiple conversion operators detected."); + + m_TokenStack.emplace_back(token::OperatorKeyword{}); + m_HasConversionOperator = true; + } + else if (constexpr std::array collection{classKeyword, structKeyword, enumKeyword}; + util::contains(collection, keyword)) + { + // This token is needed, so we do not accidentally treat e.g. `(anonymous class)` as function args, + // because otherwise there would just be the `anonymous` identifier left. + m_TokenStack.emplace_back(token::TypeContext{.content = content}); + } + else if (util::contains(typeKeywordCollection, keyword)) + { + m_TokenStack.emplace_back( + token::Identifier{ + .isBuiltinType = true, + .content = content}); + } + } + + void ParserImpl::handle_lexer_token(std::string_view const content, lexing::token::OperatorOrPunctuator const& token) + { + if (scopeResolution == token) + { + token::try_reduce_as_function_identifier(m_TokenStack); + + m_TokenStack.emplace_back( + std::in_place_type, + content); + token::try_reduce_as_scope_sequence(m_TokenStack); + } + else if (commaSeparator == token) + { + if (is_suffix_of(m_TokenStack) + || token::try_reduce_as_type(m_TokenStack)) + { + token::try_reduce_as_arg_sequence(m_TokenStack); + } + + m_TokenStack.emplace_back( + std::in_place_type, + content); + } + else if (lvalueRef == token) + { + auto& specs = token::get_or_emplace_specs(m_TokenStack); + CTNP_ASSERT(token::Specs::Refness::none == specs.refness, "Specs already is a reference."); + specs.refness = token::Specs::Refness::lvalue; + } + else if (rvalueRef == token) + { + auto& specs = token::get_or_emplace_specs(m_TokenStack); + CTNP_ASSERT(token::Specs::Refness::none == specs.refness, "Specs already is a reference."); + specs.refness = token::Specs::Refness::rvalue; + } + else if (pointer == token) + { + auto& specs = token::get_or_emplace_specs(m_TokenStack); + specs.layers.emplace_back(); + } + else if (openingAngle == token) + { + m_TokenStack.emplace_back( + std::in_place_type, + content); + } + else if (closingAngle == token) + { + if (is_suffix_of(m_TokenStack) + || token::try_reduce_as_type(m_TokenStack)) + { + token::try_reduce_as_arg_sequence(m_TokenStack); + } + + m_TokenStack.emplace_back( + std::in_place_type, + content); + token::try_reduce_as_template_identifier(m_TokenStack) + || token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); + } + else if (openingParens == token) + { + m_TokenStack.emplace_back( + std::in_place_type, + content); + } + else if (closingParens == token) + { + bool isNextOpeningParens{false}; + if (auto const* const nextOp = peek_if()) + { + isNextOpeningParens = (openingParens == *nextOp); + } + + // There can be no `(` directly after function-args, thus do not perform any reduction if such a token is found. + // This helps when function-ptrs are given, so that we do not accidentally reduce something like `(__cdecl*)` as function-args. + if (!isNextOpeningParens) + { + if (is_suffix_of(m_TokenStack) + || token::try_reduce_as_type(m_TokenStack)) + { + token::try_reduce_as_arg_sequence(m_TokenStack); + } + } + + m_TokenStack.emplace_back( + std::in_place_type, + content); + + if (bool const result = isNextOpeningParens + ? token::try_reduce_as_function_ptr(m_TokenStack) + : token::try_reduce_as_function_context(m_TokenStack); + !result) + { + token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); + } + } + else if (openingCurly == token) + { + m_TokenStack.emplace_back( + std::in_place_type, + content); + } + else if (closingCurly == token) + { + m_TokenStack.emplace_back( + std::in_place_type, + content); + token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); + } + else if (backtick == token) + { + m_TokenStack.emplace_back( + std::in_place_type, + content); + } + else if (singleQuote == token) + { + if (token::try_reduce_as_function_identifier(m_TokenStack)) + { + unwrap_msvc_like_function(); + } + // Something like `id1::id2' should become id1::id2, so just remove the leading backtick. + else if (is_suffix_of(m_TokenStack)) + { + m_TokenStack.erase(m_TokenStack.cend() - 3u); + } + else + { + m_TokenStack.emplace_back( + std::in_place_type, + content); + // Well, some environments wrap in `' (like msvc) and some wrap in '' (libc++). + token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack) + || token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); + } + } + // The current parsing process will never receive an `<<` or `>>` without a preceding `operator` keyword. + // As the current `operator` parsing currently consumes the next op-symbol, we will never reach this point + // with an actual left or right-shift. So, to make that easier, just split them. + else if (leftShift == token) + { + handle_lexer_token(content.substr(0, 1u), openingAngle); + handle_lexer_token(content.substr(1u, 1u), openingAngle); + } + else if (rightShift == token) + { + handle_lexer_token(content.substr(0, 1u), closingAngle); + handle_lexer_token(content.substr(1u, 1u), closingAngle); + } + // The msvc c++23 `std::stacktrace` implementation adds `+0x\d+` to function identifiers. + // The only reason to receive a `+`-token without an `operator`-token is exactly that case. + // So, just ignore it and skip the next identifier. + else if (plus == token) + { + if (auto const* const nextId = peek_if(); + nextId + && nextId->content.starts_with("0x")) + { + std::ignore = m_Lexer.next(); + } + } + // The msvc c++23 `std::stacktrace` implementation seems to add something which looks like the executable-name as prefix. + // The only reason to receive a `!`-token without an `operator`-token is exactly that case. + // So, just ignore it and skip the previous identifier. + else if (exclamationMark == token && is_suffix_of(m_TokenStack)) + { + m_TokenStack.pop_back(); + } + } +} diff --git a/tests/parsing/CMakeLists.txt b/tests/parsing/CMakeLists.txt index 0abbb11..f8446ee 100644 --- a/tests/parsing/CMakeLists.txt +++ b/tests/parsing/CMakeLists.txt @@ -5,6 +5,7 @@ target_sources(${TARGET_NAME} PRIVATE + "Parser.cpp" "Reductions.cpp" "Tokens.cpp" ) diff --git a/tests/parsing/Parser.cpp b/tests/parsing/Parser.cpp new file mode 100644 index 0000000..0882226 --- /dev/null +++ b/tests/parsing/Parser.cpp @@ -0,0 +1,2619 @@ +// Copyright Dominic (DNKpp) Koepke 2025 - 2025. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "ctnp/parsing/Parser.hpp" + +using namespace ctnp; + +namespace +{ + struct VisitorMock + { + mimicpp::Mock unrecognized{{.name = "VisitorMock::unrecognized"}}; + + mimicpp::Mock begin{{.name = "VisitorMock::begin"}}; + mimicpp::Mock end{{.name = "VisitorMock::end"}}; + + mimicpp::Mock add_identifier{{.name = "VisitorMock::add_identifier"}}; + mimicpp::Mock add_arg{{.name = "VisitorMock::add_arg"}}; + + mimicpp::Mock begin_scope{{.name = "VisitorMock::begin_scope"}}; + mimicpp::Mock end_scope{{.name = "VisitorMock::end_scope"}}; + + mimicpp::Mock begin_type{{.name = "VisitorMock::begin_type"}}; + mimicpp::Mock end_type{{.name = "VisitorMock::end_type"}}; + + mimicpp::Mock begin_template_args{{.name = "VisitorMock::begin_template_args"}}; + mimicpp::Mock end_template_args{{.name = "VisitorMock::end_template_args"}}; + + mimicpp::Mock begin_function{{.name = "VisitorMock::begin_function"}}; + mimicpp::Mock end_function{{.name = "VisitorMock::end_function"}}; + mimicpp::Mock begin_return_type{{.name = "VisitorMock::begin_return_type"}}; + mimicpp::Mock end_return_type{{.name = "VisitorMock::end_return_type"}}; + mimicpp::Mock begin_function_args{{.name = "VisitorMock::begin_function_args"}}; + mimicpp::Mock end_function_args{{.name = "VisitorMock::end_function_args"}}; + + mimicpp::Mock begin_function_ptr{{.name = "VisitorMock::begin_function_ptr"}}; + mimicpp::Mock end_function_ptr{{.name = "VisitorMock::end_function_ptr"}}; + + mimicpp::Mock begin_operator_identifier{{.name = "VisitorMock::begin_operator_identifier"}}; + mimicpp::Mock end_operator_identifier{{.name = "VisitorMock::end_operator_identifier"}}; + + mimicpp::Mock add_const{{.name = "VisitorMock::add_const"}}; + mimicpp::Mock add_volatile{{.name = "VisitorMock::add_volatile"}}; + mimicpp::Mock add_noexcept{{.name = "VisitorMock::add_noexcept"}}; + mimicpp::Mock add_ptr{{.name = "VisitorMock::add_ptr"}}; + mimicpp::Mock add_lvalue_ref{{.name = "VisitorMock::add_lvalue_ref"}}; + mimicpp::Mock add_rvalue_ref{{.name = "VisitorMock::add_rvalue_ref"}}; + + [[nodiscard]] + auto expect_spec_call(std::string_view const content) + { + if ("const" == content) + { + return add_const.expect_call(); + } + + if ("volatile" == content) + { + return add_volatile.expect_call(); + } + + if ("noexcept" == content) + { + return add_noexcept.expect_call(); + } + + if ("*" == content) + { + return add_ptr.expect_call(); + } + + if ("&" == content) + { + return add_lvalue_ref.expect_call(); + } + + if ("&&" == content) + { + return add_rvalue_ref.expect_call(); + } + + mimicpp::util::unreachable(); + } + }; + + constexpr std::array placeholderCollection = std::to_array({ + "{placeholder}", + "{__placeholder}", + "{place holder}", + "{place-holder}", + "{anon class}", + + //"(placeholder)", this will never be supported as we can not reliably distinguish that from FunctionArgs + //"(__placeholder)", + "(place holder)", + "(place-holder)", + "(anon class)", + + "", + "<__placeholder>", + "", + "", + "", + + "`placeholder'", + "`__placeholder'", + "`place holder'", + "`place-holder'", + "`anon class'", + + "'placeholder'", + "'__placeholder'", + "'place holder'", + "'place-holder'", + "'anon class'", + }); +} + +TEST_CASE( + "parsing::NameParser rejects unrecognizable input.", + "[print][print::type]") +{ + std::string_view constexpr input{"Hello, World!"}; + + VisitorMock visitor{}; + + SCOPED_EXP visitor.unrecognized.expect_call("Hello, World!"); + + parsing::Parser parser{std::ref(visitor), input}; + SECTION("When parsing type.") + { + parser.parse_type(); + } + + SECTION("When parsing function.") + { + parser.parse_function(); + } +} + +TEST_CASE( + "parsing::NameParser supports builtin-types.", + "[parsing]") +{ + std::string const type = GENERATE( + "auto", + "int", + "float", + "double", + "unsigned", + "signed", + "unsigned char", + "signed char", + "long long", + "unsigned long long", + "signed long long"); + + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("When plain type is given.") + { + std::string const input = type; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(input); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When qualified type is given.") + { + std::string const qualification = GENERATE("&", "&&", "*"); + std::string const spacing = GENERATE("", " "); + std::string const input = type + spacing + qualification; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(type); + sequence += visitor.expect_spec_call(qualification); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When type is used as template-argument.") + { + std::string const input = "foo<" + type + ">"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(1); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(type); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When type is used as function-argument.") + { + std::string const input = "ret (" + type + ")"; + CAPTURE(input); + + sequence += visitor.begin_function.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("ret"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_args.expect_call(1); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(type); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When type is used as return-type.") + { + std::string const input = type + " ()"; + CAPTURE(input); + + sequence += visitor.begin_function.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(type); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } +} + +TEST_CASE( + "parsing::NameParser detects identifiers.", + "[parsing]") +{ + static constexpr std::array identifiers = std::to_array({"foo", "_123", "foo456", "const_", "_const"}); + + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("When single identifier is given.") + { + std::string_view const input = GENERATE(from_range(identifiers)); + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(input); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When multiple scopes are given.") + { + std::string_view const firstScope = GENERATE(from_range(identifiers)); + std::string_view const secondScope = GENERATE(from_range(identifiers)); + std::string_view const thirdScope = GENERATE(from_range(identifiers)); + + std::string const input = std::string{firstScope} + "::" + std::string{secondScope} + "::" + std::string{thirdScope}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call(firstScope); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call(secondScope); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call(thirdScope); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } +} + +TEST_CASE( + "parsing::NameParser detects specifications.", + "[parsing]") +{ + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("When given before the actual identifier.") + { + std::string const spec = GENERATE("const", "volatile"); + std::string const input{spec + " foo"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.expect_spec_call(spec); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When given before the actual identifier with ref/pointer.") + { + std::string const spec = GENERATE("const", "volatile"); + std::string const indirection = GENERATE("&", "&&", "*"); + std::string const input{spec + " foo " + indirection}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.expect_spec_call(spec); + sequence += visitor.expect_spec_call(indirection); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When given after the actual identifier.") + { + std::string const spec = GENERATE("const", "volatile"); + std::string const input{"foo " + spec}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.expect_spec_call(spec); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When given after the actual identifier with ref/pointer.") + { + std::string const spec = GENERATE("const", "volatile"); + std::string const indirection = GENERATE("&", "&&", "*"); + std::string const input{"foo " + spec + indirection}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.expect_spec_call(spec); + sequence += visitor.expect_spec_call(indirection); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Coming all together") + { + std::string const input{"volatile foo const* volatile** const&"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } +} + +TEST_CASE( + "parsing::NameParser handles decorated types.", + "[parsing]") +{ + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + std::string const indirection = GENERATE("&", "&&", "*"); + std::string const spacing = GENERATE("", " "); + std::string const input = "int* __ptr64" + spacing + indirection + " __ptr64"; + CAPTURE(indirection, input); + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.expect_spec_call(indirection); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); +} + +TEST_CASE( + "parsing::NameParser detects templates.", + "[parsing]") +{ + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("When templated identifier with 0 args is given.") + { + std::string_view const input{"foo<>"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(0); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When templated identifier with placeholder arg is given.") + { + std::string const placeholder{GENERATE(from_range(placeholderCollection))}; + std::string const input = "foo<" + placeholder + ">"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(1); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(placeholder); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When qualified templated identifier is given.") + { + std::string_view const input{"volatile foo<> const&"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(0); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When templated identifier with multiple args is given.") + { + std::string_view const input{"foo"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(2); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.add_arg.expect_call(); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("std"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("string"); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When templated identifier with multiple args with specs is given.") + { + std::string_view const input{"foo"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(2); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + sequence += visitor.end_type.expect_call(); + sequence += visitor.add_arg.expect_call(); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("std"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("string"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When templated identifier has templated arg.") + { + std::string_view const input{"foo>"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(1); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("bar"); + sequence += visitor.begin_template_args.expect_call(0); + sequence += visitor.end_template_args.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When templated identifier has qualified templated arg.") + { + std::string_view const input{"foo volatile&>"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(1); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("bar"); + + { + sequence += visitor.begin_template_args.expect_call(1); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end_template_args.expect_call(); + } + + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When template is part of a scope.") + { + std::string_view const input{"foo<>::bar"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.begin_template_args.expect_call(0); + sequence += visitor.end_template_args.expect_call(); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("bar"); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } +} + +TEST_CASE( + "parsing::NameParser detects named functions.", + "[parsing]") +{ + std::string const spec = GENERATE("", "const", "volatile", "noexcept", "&", "&&"); + std::string const specSpace = GENERATE("", " "); + std::string const suffix = specSpace + spec; + + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_function.expect_call(); + + SECTION("When function identifier with 0 args is given.") + { + std::string const returnType = GENERATE("", "void"); + std::string const prefix = returnType + (returnType.empty() ? "" : " "); + std::string const input = prefix + "foo()" + suffix; + CAPTURE(input); + + CHECKED_IF(!returnType.empty()) + { + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(returnType); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + } + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!spec.empty()) + { + sequence += visitor.expect_spec_call(spec); + } + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("When templated function identifier is given.") + { + std::string const returnType = GENERATE("", "void"); + std::string const prefix = returnType + (returnType.empty() ? "" : " "); + std::string const input = prefix + "foo()" + suffix; + CAPTURE(input); + + CHECKED_IF(!returnType.empty()) + { + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(returnType); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + } + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(1); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!spec.empty()) + { + sequence += visitor.expect_spec_call(spec); + } + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("When function identifier with single arg is given.") + { + std::string const returnType = GENERATE("", "void"); + std::string const prefix = returnType + (returnType.empty() ? "" : " "); + std::string const input = prefix + "foo(const std::string)" + suffix; + CAPTURE(input); + + CHECKED_IF(!returnType.empty()) + { + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(returnType); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + } + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_function_args.expect_call(1); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("std"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("string"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!spec.empty()) + { + sequence += visitor.expect_spec_call(spec); + } + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("When function identifier with templated arg is given.") + { + std::string const returnType = GENERATE("", "void"); + std::string const prefix = returnType + (returnType.empty() ? "" : " "); + std::string const input = prefix + "foo(volatile bar const&)" + suffix; + CAPTURE(input); + + CHECKED_IF(!returnType.empty()) + { + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(returnType); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + } + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_function_args.expect_call(1); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("bar"); + + sequence += visitor.begin_template_args.expect_call(1); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!spec.empty()) + { + sequence += visitor.expect_spec_call(spec); + } + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("When function identifier with multiple args is given.") + { + std::string const returnType = GENERATE("", "void"); + std::string const prefix = returnType + (returnType.empty() ? "" : " "); + std::string const input = prefix + "foo(const char&&, const int)" + suffix; + CAPTURE(input); + + CHECKED_IF(!returnType.empty()) + { + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(returnType); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + } + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_function_args.expect_call(2); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("char"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_rvalue_ref.expect_call(); + sequence += visitor.end_type.expect_call(); + sequence += visitor.add_arg.expect_call(); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!spec.empty()) + { + sequence += visitor.expect_spec_call(spec); + } + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("When function is a scope.") + { + std::string const returnType = GENERATE("", "void"); + std::string const prefix = returnType + (returnType.empty() ? "" : " "); + std::string const templateExpr = GENERATE("", "<>"); + std::string const scopeSpec = GENERATE("", "const", "volatile", "&", "&&", "noexcept"); + std::string const input = prefix + "foo" + templateExpr + "()" + scopeSpec + "::bar::fun()" + suffix; + CAPTURE(input); + + CHECKED_IF(!returnType.empty()) + { + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(returnType); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + } + + { + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.begin_function.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + CHECKED_IF(!templateExpr.empty()) + { + sequence += visitor.begin_template_args.expect_call(0); + sequence += visitor.end_template_args.expect_call(); + } + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!scopeSpec.empty()) + { + sequence += visitor.expect_spec_call(scopeSpec); + } + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end_scope.expect_call(); + } + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("bar"); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("fun"); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!spec.empty()) + { + sequence += visitor.expect_spec_call(spec); + } + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("When function identifier with qualified return type is given.") + { + std::string const input = "const std::string* volatile& foo()" + suffix; + CAPTURE(input); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("std"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("string"); + + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!spec.empty()) + { + sequence += visitor.expect_spec_call(spec); + } + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("When function identifier with templated return type is given.") + { + std::string const input = "bar& foo()" + suffix; + CAPTURE(input); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call("bar"); + sequence += visitor.begin_template_args.expect_call(1); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_template_args.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!spec.empty()) + { + sequence += visitor.expect_spec_call(spec); + } + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } +} + +TEST_CASE( + "parsing::NameParser detects function types.", + "[parsing]") +{ + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + std::string const spec = GENERATE("", "const", "volatile", "noexcept", "&", "&&"); + std::string const specSpace = GENERATE("", " "); + std::string const suffix = specSpace + spec; + + sequence += visitor.begin.expect_call(); + + SECTION("When function has an unqualified return-type.") + { + std::string const input = "foo ()" + suffix; + CAPTURE(input); + + sequence += visitor.begin_function.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!spec.empty()) + { + sequence += visitor.expect_spec_call(spec); + } + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When function has a qualified return-type.") + { + std::string const input = "volatile foo const& ()" + suffix; + CAPTURE(input); + + sequence += visitor.begin_function.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!spec.empty()) + { + sequence += visitor.expect_spec_call(spec); + } + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When function is template argument.") + { + std::string const input = "foo"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(1); + { + sequence += visitor.begin_function.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!spec.empty()) + { + sequence += visitor.expect_spec_call(spec); + } + + sequence += visitor.end_function.expect_call(); + } + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } +} + +TEST_CASE( + "parsing::NameParser detects placeholders.", + "[parsing]") +{ + std::string const placeholder{GENERATE(from_range(placeholderCollection))}; + + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("Plain placeholders are detected.") + { + std::string_view const input = placeholder; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call(input); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Templated placeholders are detected.") + { + std::string const input = placeholder + "<>"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call(placeholder); + + sequence += visitor.begin_template_args.expect_call(0); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Prefix qualified placeholders are detected.") + { + std::string const spec = GENERATE("const", "volatile"); + std::string const input = spec + " " + placeholder; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call(placeholder); + sequence += visitor.expect_spec_call(spec); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Suffix qualified placeholders are detected.") + { + std::string const spec = GENERATE("const", "volatile", "&", "&&", "*"); + std::string const input = placeholder + " " + spec; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call(placeholder); + sequence += visitor.expect_spec_call(spec); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Fully qualified placeholders are detected.") + { + std::string const input = "volatile " + placeholder + " const&"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call(placeholder); + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Scoped placeholders are detected.") + { + std::string const input = "foo::" + placeholder + "::my_type"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call(placeholder); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("my_type"); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Placeholder return types are detected.") + { + std::string const input = placeholder + " foo()"; + CAPTURE(input); + + sequence += visitor.begin_function.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(placeholder); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("Functions with placeholder names are detected.") + { + std::string const returnType = GENERATE("", "void"); + std::string const prefix = returnType + (returnType.empty() ? "" : " "); + std::string const input = prefix + placeholder + "()"; + CAPTURE(input); + + sequence += visitor.begin_function.expect_call(); + + CHECKED_IF(!returnType.empty()) + { + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(returnType); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + } + + sequence += visitor.add_identifier.expect_call(placeholder); + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("Functions with placeholder scoped names are detected.") + { + std::string const returnType = GENERATE("", "void"); + std::string const prefix = returnType + (returnType.empty() ? "" : " "); + std::string const input = prefix + placeholder + "::foo()"; + CAPTURE(input); + + sequence += visitor.begin_function.expect_call(); + + CHECKED_IF(!returnType.empty()) + { + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call(returnType); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + } + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call(placeholder); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } +} + +TEST_CASE( + "parsing::NameParser detects function pointers.", + "[parsing]") +{ + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("When function pointer without arguments is given.") + { + std::string const input = "const std::string* volatile& (*)()"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("std"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("string"); + + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When noexcept function pointer without arguments is given.") + { + std::string const input = "void (*)()noexcept"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.add_noexcept.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When function pointer-ref without arguments is given.") + { + std::string const input = "void (*&)()noexcept"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.add_noexcept.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When function pointer with arguments is given.") + { + std::string const input = "void (*)(const std::string&&, const int)"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + + sequence += visitor.begin_function_args.expect_call(2); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("std"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("string"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_rvalue_ref.expect_call(); + sequence += visitor.end_type.expect_call(); + sequence += visitor.add_arg.expect_call(); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When member function pointer without arguments is given.") + { + std::string const input = "void (foo::bar::*)()"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("bar"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When member function pointer with qualifications is given.") + { + std::string const spec = GENERATE("const", "volatile", "noexcept", "&", "&&"); + std::string const input = "void (foo::bar::*)()" + spec; + CAPTURE(input, spec); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("bar"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.expect_spec_call(spec); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When return-type is a function pointer.") + { + std::string const input = "void (*(float))(int)"; + CAPTURE(input); + + sequence += visitor.begin_function.expect_call(); + + { // handles the `void (*)(int)` return-type. + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + + sequence += visitor.begin_function_args.expect_call(1); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + } + + sequence += visitor.begin_function_args.expect_call(1); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("float"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When Function-Ptr with a function-ptr return-type is given.") + { + std::string const input = "void (*(*)(float))(int)"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + + { // Handles the `void (*)(float)` return-type + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + + sequence += visitor.begin_function_args.expect_call(1); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_function_args.expect_call(); + } + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + + sequence += visitor.begin_function_args.expect_call(1); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("float"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When function-ptr with function-ptr parameter is given.") + { + std::string const input = "void (*)(void (*)())"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + + sequence += visitor.begin_function_args.expect_call(1); + + { + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + } + + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When parameter is a template argument.") + { + std::string const input = "foo"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(1); + + { + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + } + + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } +} + +TEST_CASE( + "parsing::NameParser handles decorated function-ptrs.", + "[parsing]") +{ + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("When function-ptr is given.") + { + std::string const input = "void (__cdecl*)()"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When member function-ptr is given.") + { + std::string const input = "void (__cdecl foo::*)()"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_ptr.expect_call(); + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } +} + +TEST_CASE( + "parsing::NameParser handles arbitrarily scoped identifiers.", + "[parsing]") +{ + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + auto expect_args = [&] { + { + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("std"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("string"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.add_arg.expect_call(); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.end_type.expect_call(); + } + }; + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_type.expect_call(); + + SECTION("When function local type is given.") + { + constexpr std::string_view input{"foo(std::string const&, int volatile) noexcept::my_type"}; + CAPTURE(input); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.begin_function.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_function_args.expect_call(2); + expect_args(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.add_noexcept.expect_call(); + sequence += visitor.end_function.expect_call(); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("my_type"); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When qualified function local type is given.") + { + constexpr std::string_view input{"volatile foo(std::string const&, int volatile) noexcept::my_type const&"}; + CAPTURE(input); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.begin_function.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_function_args.expect_call(2); + expect_args(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.add_noexcept.expect_call(); + sequence += visitor.end_function.expect_call(); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("my_type"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When nested function local type is given.") + { + constexpr std::string_view input{"foo::bar(std::string const&, int volatile) noexcept::my_type"}; + CAPTURE(input); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.begin_function.expect_call(); + sequence += visitor.add_identifier.expect_call("bar"); + + sequence += visitor.begin_function_args.expect_call(2); + expect_args(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.add_noexcept.expect_call(); + sequence += visitor.end_function.expect_call(); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("my_type"); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When deeply nested function local type is given.") + { + constexpr std::string_view input{"foo() const &&::bar(std::string const&, int volatile) noexcept::my_type"}; + CAPTURE(input); + + { // foo() const && + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.begin_function.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(2); + + { + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.add_arg.expect_call(); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("std"); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("string"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + sequence += visitor.end_type.expect_call(); + } + + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_rvalue_ref.expect_call(); + sequence += visitor.end_function.expect_call(); + sequence += visitor.end_scope.expect_call(); + } + + { // bar(std::string const&, int volatile) noexcept + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.begin_function.expect_call(); + sequence += visitor.add_identifier.expect_call("bar"); + + sequence += visitor.begin_function_args.expect_call(2); + expect_args(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.add_noexcept.expect_call(); + sequence += visitor.end_function.expect_call(); + sequence += visitor.end_scope.expect_call(); + } + + sequence += visitor.add_identifier.expect_call("my_type"); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } +} + +TEST_CASE( + "parsing::NameParser handles msvc-like wrapped scopes.", + "[parsing]") +{ + std::string const spacing = GENERATE("", " "); + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("When scoped identifier is given, it's unwrapped.") + { + std::string const input = "`foo::bar'"; + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("bar"); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When function local type is given.") + { + std::string const input{"`void foo()" + spacing + "'::my_type"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + { + sequence += visitor.begin_function.expect_call(); + + /*sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call();*/ + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + } + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("my_type"); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Deeply nested function local type is given.") + { + std::string const input{ + "`ret2 `ret1 `ret0 inner::fn0(int const)'::`my_placeholder'::middle::fn1()const'::outer::fn2()const &" + spacing + "'::my_type"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("inner"); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + { // ``ret0 fn0(int const)'` + sequence += visitor.begin_function.expect_call(); + + /*sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("ret0"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call();*/ + + sequence += visitor.add_identifier.expect_call("fn0"); + + sequence += visitor.begin_function_args.expect_call(1); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("int"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + } + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("`my_placeholder'"); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("middle"); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + { // ``ret1 fn1()const'` + sequence += visitor.begin_function.expect_call(); + + /*sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("ret1"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call();*/ + + sequence += visitor.add_identifier.expect_call("fn1"); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + sequence += visitor.add_const.expect_call(); + + sequence += visitor.end_function.expect_call(); + } + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("outer"); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + { // ``ret2 fn2()const'` + sequence += visitor.begin_function.expect_call(); + + /*sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("ret2"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call();*/ + + sequence += visitor.add_identifier.expect_call("fn2"); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + + sequence += visitor.end_function.expect_call(); + } + sequence += visitor.end_scope.expect_call(); + // end `outer::fn2()const` + + sequence += visitor.add_identifier.expect_call("my_type"); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When decorated function local type is given.") + { + std::string const visibility = GENERATE("public", "private", "protected"); + std::string const typeClass = GENERATE("struct", "class", "enum"); + std::string const refness = GENERATE("", "&", "&&"); + std::string const input = typeClass + " `" + visibility + ": void __cdecl foo() __ptr64" + spacing + refness + "'::my_type"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + { + sequence += visitor.begin_function.expect_call(); + + /*sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call();*/ + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + CHECKED_IF(!refness.empty()) + { + sequence += visitor.expect_spec_call(refness); + } + + sequence += visitor.end_function.expect_call(); + } + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("my_type"); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When decorated lambda is given.") + { + std::string const input = "struct std::source_location __cdecl ::operator ()(void) const"; + CAPTURE(input); + + sequence += visitor.begin_function.expect_call(); + + { + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("std"); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("source_location"); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + } + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call(""); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_operator_identifier.expect_call(); + sequence += visitor.add_identifier.expect_call("()"); + sequence += visitor.end_operator_identifier.expect_call(); + + // `void` function-arg is omitted + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.add_const.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } +} + +TEST_CASE( + "parsing::NameParser handles msvc's std::stacktrace special characteristics.", + "[parsing]") +{ + std::string const identifier = "executable!foo+0x1337"; + + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), identifier}; + parser.parse_type(); +} + +TEST_CASE( + "parsing::NameParser keeps meaningful reserved identifiers.", + "[parsing]") +{ + std::string const identifier = "__identifier"; + + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("Just a single identifier.") + { + std::string const input = identifier; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call(identifier); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), identifier}; + parser.parse_type(); + } + + SECTION("Prefix qualified identifiers.") + { + std::string const spec = GENERATE("const", "volatile"); + std::string const input = spec + " " + identifier; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call(identifier); + sequence += visitor.expect_spec_call(spec); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Suffix qualified identifiers.") + { + std::string const spec = GENERATE("const", "volatile", "&", "&&", "*"); + std::string const input = identifier + " " + spec; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call(identifier); + sequence += visitor.expect_spec_call(spec); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Fully qualified identifiers.") + { + std::string const input = "volatile " + identifier + " const&"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call(identifier); + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("As function name.") + { + std::string const input = identifier + "()"; + CAPTURE(input); + + sequence += visitor.begin_function.expect_call(); + + sequence += visitor.add_identifier.expect_call(identifier); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("As template.") + { + std::string const input = identifier + "<>"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.add_identifier.expect_call(identifier); + + sequence += visitor.begin_template_args.expect_call(0); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } +} + +TEST_CASE( + "parsing::NameParser detects operators as identifiers.", + "[parsing]") +{ + // see: https://en.cppreference.com/w/cpp/language/operators + auto const [requireSpacing, operatorSymbol] = GENERATE( + (table)({ + {false, "+"}, + {false, "-"}, + {false, "*"}, + {false, "/"}, + {false, "%"}, + {false, "^"}, + {false, "&"}, + {false, "|"}, + {false, "~"}, + {false, "!"}, + {false, "="}, + {false, "<"}, + {false, ">"}, + {false, "+="}, + {false, "-="}, + {false, "*="}, + {false, "/="}, + {false, "%="}, + {false, "^="}, + {false, "&="}, + {false, "|="}, + {false, "<<"}, + {false, ">>"}, + {false, "<<="}, + {false, ">>="}, + {false, "=="}, + {false, "!="}, + {false, "<="}, + {false, ">="}, + {false, "<=>"}, + {false, "&&"}, + {false, "||"}, + {false, "++"}, + {false, "--"}, + {false, ","}, + {false, "->*"}, + {false, "->"}, + {false, "()"}, + {false, "[]"}, + { true, "new"}, + { true, "new[]"}, + { true, "new []"}, + { true, "delete"}, + { true, "delete[]"}, + { true, "delete []"}, + { true, "co_await"} + })); + + std::string const spacing = requireSpacing + ? " " + : GENERATE("", " "); + + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + SECTION("When operator + symbol form a function.") + { + std::string const input = std::string{"operator"} + spacing + std::string{operatorSymbol} + "()"; + CAPTURE(input); + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_function.expect_call(); + + sequence += visitor.begin_operator_identifier.expect_call(); + sequence += visitor.add_identifier.expect_call(operatorSymbol); + sequence += visitor.end_operator_identifier.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("When templated operator is given.") + { + std::string const templateSpacing = GENERATE("", " "); + std::string const input = std::string{"operator"} + spacing + std::string{operatorSymbol} + templateSpacing + "<>" + "()"; + CAPTURE(input); + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_function.expect_call(); + + sequence += visitor.begin_operator_identifier.expect_call(); + sequence += visitor.add_identifier.expect_call(operatorSymbol); + sequence += visitor.end_operator_identifier.expect_call(); + + sequence += visitor.begin_template_args.expect_call(0); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("When operator is scope.") + { + std::string const input = "foo::" + std::string{"operator"} + spacing + std::string{operatorSymbol} + "()::my_type"; + CAPTURE(input); + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + { + sequence += visitor.begin_function.expect_call(); + + sequence += visitor.begin_operator_identifier.expect_call(); + sequence += visitor.add_identifier.expect_call(operatorSymbol); + sequence += visitor.end_operator_identifier.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + } + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("my_type"); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_type(); + } +} + +TEST_CASE( + "parsing::NameParser detects conversion operators.", + "[parsing]") +{ + VisitorMock visitor{}; + mimicpp::ScopedSequence sequence{}; + + SECTION("When converting to simple type-name.") + { + std::string const input = "operator bool()"; + CAPTURE(input); + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_function.expect_call(); + + sequence += visitor.begin_operator_identifier.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("bool"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_operator_identifier.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } + + SECTION("When converting to complex type-name.") + { + std::string const input = "operator volatile foo::bar()::my_type const&()"; + CAPTURE(input); + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_function.expect_call(); + + sequence += visitor.begin_operator_identifier.expect_call(); + { + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_scope.expect_call(); + sequence += visitor.begin_function.expect_call(); + sequence += visitor.add_identifier.expect_call("bar"); + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + sequence += visitor.end_function.expect_call(); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.add_identifier.expect_call("my_type"); + sequence += visitor.add_const.expect_call(); + sequence += visitor.add_volatile.expect_call(); + sequence += visitor.add_lvalue_ref.expect_call(); + + sequence += visitor.end_type.expect_call(); + } + sequence += visitor.end_operator_identifier.expect_call(); + + sequence += visitor.begin_function_args.expect_call(0); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + parsing::Parser parser{std::ref(visitor), input}; + parser.parse_function(); + } +} From feee50ea63e1a99bf82c940690c36ea4086bb9dc Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 17 May 2025 21:23:25 +0200 Subject: [PATCH 6/9] fix: remove unused constant --- src/parsing/Parser.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/parsing/Parser.cpp b/src/parsing/Parser.cpp index 65e00d6..18bb70e 100644 --- a/src/parsing/Parser.cpp +++ b/src/parsing/Parser.cpp @@ -37,7 +37,6 @@ namespace ctnp::parsing::detail constexpr lexing::token::OperatorOrPunctuator pointer{"*"}; constexpr lexing::token::OperatorOrPunctuator lvalueRef{"&"}; constexpr lexing::token::OperatorOrPunctuator rvalueRef{"&&"}; - constexpr lexing::token::OperatorOrPunctuator colon{":"}; constexpr lexing::token::OperatorOrPunctuator leftShift{"<<"}; constexpr lexing::token::OperatorOrPunctuator rightShift{">>"}; constexpr lexing::token::OperatorOrPunctuator plus{"+"}; From be31a273a5dd52e28d06499110a8652987d044e0 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 17 May 2025 21:36:58 +0200 Subject: [PATCH 7/9] fix: mark param as [[maybe_unused]] --- src/parsing/Parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parsing/Parser.cpp b/src/parsing/Parser.cpp index 18bb70e..b35fc64 100644 --- a/src/parsing/Parser.cpp +++ b/src/parsing/Parser.cpp @@ -309,7 +309,7 @@ namespace ctnp::parsing::detail // util::unreachable(); } - void ParserImpl::handle_lexer_token(std::string_view const content, [[maybe_unused]] lexing::token::Space const& space) + void ParserImpl::handle_lexer_token([[maybe_unused]] std::string_view const content, [[maybe_unused]] lexing::token::Space const& space) { if (auto* const id = match_suffix(m_TokenStack)) { From 890b648e629f81d8061a6dd17f08f536fc3e6fec Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 17 May 2025 22:07:14 +0200 Subject: [PATCH 8/9] fix: please clang-16 --- include/ctnp/parsing/Parser.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/ctnp/parsing/Parser.hpp b/include/ctnp/parsing/Parser.hpp index fa9fc2c..0a17e5c 100644 --- a/include/ctnp/parsing/Parser.hpp +++ b/include/ctnp/parsing/Parser.hpp @@ -118,8 +118,9 @@ namespace ctnp::parsing void parse_function() { - detail::ResultVisitor visitor{ - .visitor = unwrap_visitor(m_Visitor), + auto& unwrapped = unwrap_visitor(m_Visitor); + detail::ResultVisitor visitor{ + .visitor = unwrapped, .content = m_Parser.content()}; std::visit(visitor, m_Parser.parse_function()); } From 358020df0330497ce9c16efcdd9af1ce72c88eb8 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 17 May 2025 22:08:10 +0200 Subject: [PATCH 9/9] chore: disable mimic++ stacktrace --- tests/cmake/Findmimicpp.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cmake/Findmimicpp.cmake b/tests/cmake/Findmimicpp.cmake index ef1ecd0..832abdb 100644 --- a/tests/cmake/Findmimicpp.cmake +++ b/tests/cmake/Findmimicpp.cmake @@ -12,6 +12,6 @@ CPMAddPackage(mimicpp EXCLUDE_FROM_ALL YES OPTIONS "MIMICPP_CONFIG_USE_FMT ON" - "MIMICPP_CONFIG_EXPERIMENTAL_STACKTRACE ON" - "MIMICPP_CONFIG_EXPERIMENTAL_USE_CPPTRACE ON" + #"MIMICPP_CONFIG_EXPERIMENTAL_STACKTRACE ON" + #"MIMICPP_CONFIG_EXPERIMENTAL_USE_CPPTRACE ON" )