From efe38d5f1092c37d99d9cec9533403720b11ce5b Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 22 Mar 2025 20:30:13 +0100 Subject: [PATCH 001/130] feat: util::prefix_range --- include/mimic++/utilities/Algorithm.hpp | 29 ++++++++++++++ test/unit-tests/utilities/Algorithm.cpp | 51 +++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/include/mimic++/utilities/Algorithm.hpp b/include/mimic++/utilities/Algorithm.hpp index 9220229ea..2120fb77f 100644 --- a/include/mimic++/utilities/Algorithm.hpp +++ b/include/mimic++/utilities/Algorithm.hpp @@ -11,6 +11,7 @@ #include "mimic++/config/Config.hpp" #include +#include #include #include #include @@ -168,6 +169,34 @@ namespace mimicpp::util return {std::ranges::end(str), std::ranges::end(str)}; } + + /** + * \brief Returns a view containing all elements, which start with the given prefix. + * \tparam Range The range type, which holds elements comparable with `Prefix`. + * \tparam Prefix The prefix type. + * \param range The range. + * \param prefix The prefix. + * \return A subrange view to `range`. + * + * \attention The behaviour is undefined, when `range` is not sorted. + */ + template + requires std::totally_ordered_with, Prefix> + [[nodiscard]] + constexpr std::ranges::borrowed_subrange_t prefix_range(Range&& range, Prefix&& prefix) + { + auto const lower = std::ranges::lower_bound(range, prefix); + auto const end = std::ranges::lower_bound( + lower, + std::ranges::end(range), + prefix, + [](auto const& element, auto const& p) { + auto const iter = std::ranges::mismatch(element, p).in2; + return iter == std::ranges::end(p); + }); + + return {lower, end}; + } } #endif diff --git a/test/unit-tests/utilities/Algorithm.cpp b/test/unit-tests/utilities/Algorithm.cpp index 8cfc896d1..e06de6ac8 100644 --- a/test/unit-tests/utilities/Algorithm.cpp +++ b/test/unit-tests/utilities/Algorithm.cpp @@ -248,3 +248,54 @@ TEST_CASE( CHECK(token.begin() == str.cend()); CHECK(token.end() == str.cend()); } + +TEST_CASE( + "util::prefix_range returns the subrange to the elements, which have the given prefix.", + "[util][util::prefix_range]") +{ + SECTION("Empty collection is supported.") + { + constexpr std::vector collection{}; + + auto const result = util::prefix_range(collection, StringViewT{"foo"}); + + CHECK_THAT( + result, + Catch::Matchers::IsEmpty()); + } + + SECTION("Empty prefix is supported.") + { + std::vector const collection{"bar", "bfoo"}; + + auto const result = util::prefix_range(collection, StringViewT{}); + + CHECK(collection.cbegin() == result.begin()); + CHECK_THAT( + result, + Catch::Matchers::RangeEquals(std::vector{"bar", "bfoo"})); + } + + SECTION("When no element starts with prefix.") + { + std::vector const collection{"bar", "bfoo"}; + + auto const result = util::prefix_range(collection, StringViewT{"foo"}); + + CHECK_THAT( + result, + Catch::Matchers::IsEmpty()); + } + + SECTION("When some elements starts with prefix.") + { + std::vector const collection{"a foo", "foo", "foo-bar", "foofoo", "no-foo"}; + + auto const result = util::prefix_range(collection, StringViewT{"foo"}); + + CHECK(collection.cbegin() + 1 == result.begin()); + CHECK_THAT( + result, + Catch::Matchers::RangeEquals(std::vector{"foo", "foo-bar", "foofoo"})); + } +} From 4ddd08afb698ad9f344fc5d314fec9ce67db40d7 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 22 Mar 2025 20:54:42 +0100 Subject: [PATCH 002/130] feat: util::concat_array --- include/mimic++/utilities/Algorithm.hpp | 53 ++++++++++++++++++++++++ test/unit-tests/utilities/Algorithm.cpp | 55 +++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/include/mimic++/utilities/Algorithm.hpp b/include/mimic++/utilities/Algorithm.hpp index 2120fb77f..fb253874c 100644 --- a/include/mimic++/utilities/Algorithm.hpp +++ b/include/mimic++/utilities/Algorithm.hpp @@ -11,11 +11,13 @@ #include "mimic++/config/Config.hpp" #include +#include #include #include #include #include #include +#include #include namespace mimicpp::util @@ -197,6 +199,57 @@ namespace mimicpp::util return {lower, end}; } + + /** + * \brief Concatenates the given arrays by copying all elements into a new array. + * \tparam T The element type. + * \tparam firstN The size of the first array. + * \tparam secondN The size of the second array. + * \param first The first array. + * \param second The second array. + * \return A newly constructed arrays with copied elements. + */ + template + [[nodiscard]] + constexpr std::array concat_arrays(std::array const& first, std::array const& second) + { + return std::invoke( + [&]( + [[maybe_unused]] std::index_sequence const, + [[maybe_unused]] std::index_sequence const) { + return std::array{ + std::get(first)..., + std::get(second)...}; + }, + std::make_index_sequence{}, + std::make_index_sequence{}); + } + + /** + * \copybrief concat_arrays + * \tparam T The element type. + * \tparam firstN The size of the first array. + * \tparam Others Other array types which share the same element-type. + * \param first The first array. + * \param others The second array. + * \return A newly constructed arrays with copied elements. + */ + template + requires(... && std::same_as>) + [[nodiscard]] + constexpr std::array)> concat_arrays(std::array const& first, Others const&... others) + { + if constexpr (0u == sizeof...(Others)) + { + return first; + } + else + { + return concat_arrays( + first, + concat_arrays(others...)); + } + } } #endif diff --git a/test/unit-tests/utilities/Algorithm.cpp b/test/unit-tests/utilities/Algorithm.cpp index e06de6ac8..1f191aca3 100644 --- a/test/unit-tests/utilities/Algorithm.cpp +++ b/test/unit-tests/utilities/Algorithm.cpp @@ -299,3 +299,58 @@ TEST_CASE( Catch::Matchers::RangeEquals(std::vector{"foo", "foo-bar", "foofoo"})); } } + +TEST_CASE( + "util::concat_arrays concatenates an arbitrary amount of arrays.", + "[util][util::concat_arrays]") +{ + SECTION("Single array is supported.") + { + constexpr std::array source{42, 1337}; + + constexpr auto result = util::concat_arrays(source); + STATIC_CHECK(std::same_as>); + + CHECK_THAT( + result, + Catch::Matchers::RangeEquals(source)); + } + + SECTION("Two arrays are supported.") + { + constexpr std::array first{42, 1337}; + constexpr std::array second{-42, -1337, 42}; + + constexpr auto result = util::concat_arrays(first, second); + STATIC_CHECK(std::same_as>); + + CHECK_THAT( + result, + Catch::Matchers::RangeEquals(std::array{42, 1337, -42, -1337, 42})); + } + + SECTION("Arbitrary amount of arrays are supported.") + { + constexpr std::array first{42, 1337}; + constexpr std::array second{-42, -1337, 42}; + + constexpr auto result = util::concat_arrays(first, second, first); + STATIC_CHECK(std::same_as>); + + CHECK_THAT( + result, + Catch::Matchers::RangeEquals(std::array{42, 1337, -42, -1337, 42, 42, 1337})); + } + + SECTION("Empty arrays are supported.") + { + constexpr std::array source{}; + + constexpr auto result = util::concat_arrays(source, source); + STATIC_CHECK(std::same_as>); + + CHECK_THAT( + result, + Catch::Matchers::IsEmpty()); + } +} From a670439ed27d7a2c22f660c42744dc8a934f755f Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sun, 23 Mar 2025 01:37:29 +0100 Subject: [PATCH 003/130] feat: printing::type::lexing::NameLexer --- include/mimic++/printing/type/NameLexer.hpp | 322 +++++++++++++ test/unit-tests/printing/CMakeLists.txt | 1 + test/unit-tests/printing/TypeNameLexer.cpp | 482 ++++++++++++++++++++ 3 files changed, 805 insertions(+) create mode 100644 include/mimic++/printing/type/NameLexer.hpp create mode 100644 test/unit-tests/printing/TypeNameLexer.cpp diff --git a/include/mimic++/printing/type/NameLexer.hpp b/include/mimic++/printing/type/NameLexer.hpp new file mode 100644 index 000000000..bd8122b6f --- /dev/null +++ b/include/mimic++/printing/type/NameLexer.hpp @@ -0,0 +1,322 @@ +// Copyright Dominic (DNKpp) Koepke 2024 - 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 MIMICPP_PRINTING_TYPE_NAME_LEXER_HPP +#define MIMICPP_PRINTING_TYPE_NAME_LEXER_HPP + +#pragma once + +#include "mimic++/Fwd.hpp" +#include "mimic++/config/Config.hpp" +#include "mimic++/utilities/Algorithm.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace mimicpp::printing::type::lexing +{ + // see: https://en.cppreference.com/w/cpp/string/byte/isspace + constexpr auto is_space = [](char const c) noexcept { + return static_cast(std::isspace(static_cast(c))); + }; + + // see: https://en.cppreference.com/w/cpp/string/byte/isalpha + constexpr auto is_digit = [](char const c) noexcept { + return static_cast(std::isdigit(static_cast(c))); + }; + + // see: https://en.cppreference.com/w/cpp/string/byte/isdigit + constexpr auto is_alpha = [](char const c) noexcept { + return static_cast(std::isdigit(static_cast(c))); + }; + + // see: https://en.cppreference.com/w/cpp/string/byte/isxdigit + constexpr auto is_hex_digit = [](char const c) noexcept { + return static_cast(std::isxdigit(static_cast(c))); + }; + + struct space + { + StringViewT content; + + [[nodiscard]] + bool operator==(space const&) const = default; + }; + + struct keyword + { + StringViewT content; + + [[nodiscard]] + bool operator==(keyword const&) const = default; + }; + + struct comma + { + StringViewT content; + + [[nodiscard]] + bool operator==(comma const&) const = default; + }; + + struct scope_resolution + { + StringViewT content; + + [[nodiscard]] + bool operator==(scope_resolution const&) const = default; + }; + + struct operator_or_punctuator + { + StringViewT content; + + [[nodiscard]] + bool operator==(operator_or_punctuator const&) const = default; + }; + + struct identifier + { + StringViewT content; + + [[nodiscard]] + bool operator==(identifier const&) const = default; + }; + + struct end + { + [[nodiscard]] + bool operator==(end const&) const = default; + }; + + using token = std::variant< + end, + space, + keyword, + comma, + scope_resolution, + operator_or_punctuator, + identifier>; + + namespace texts + { + // just list the noteworthy ones here + constexpr std::array keywords = std::to_array({"const", "constexpr", "volatile", "noexcept", "operator"}); + constexpr std::array digraphs = std::to_array({"and", "or", "xor", "not", "bitand", "bitor", "compl", "and_eq", "or_eq", "xor_eq", "not_eq"}); + + constexpr std::array braceLikes = std::to_array({"{", "}", "[", "]", "(", ")", "`", "'"}); + constexpr std::array comparison = std::to_array({"==", "!=", "<", "<=", ">", ">=", "<=>"}); + constexpr std::array assignment = std::to_array({"=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>="}); + constexpr std::array incOrDec = std::to_array({"++", "--"}); + constexpr std::array arithmetic = std::to_array({"+", "-", "*", "/", "%"}); + constexpr std::array bitArithmetic = std::to_array({"~", "&", "|", "^"}); + constexpr std::array logical = std::to_array({"!", "&&", "||"}); + constexpr std::array access = std::to_array({".", ".*", "->", "->*"}); + constexpr std::array specialAngles = std::to_array({"<:", ":>", "<%", "%>"}); + constexpr std::array rest = std::to_array({"::", ";", ",", ":", "...", "?"}); + } + + constexpr std::array keywordCollection = std::invoke( + [] { + std::array collection = util::concat_arrays( + texts::keywords, + texts::digraphs); + + std::ranges::sort(collection); + MIMICPP_ASSERT(collection.cend() == std::ranges::unique(collection).begin(), "Fix your input!"); + + return collection; + }); + + // see: https://eel.is/c++draft/lex.operators#nt:operator-or-punctuator + constexpr std::array operatorOrPunctuatorCollection = std::invoke( + [] { + std::array collection = util::concat_arrays( + texts::braceLikes, + texts::comparison, + texts::assignment, + texts::incOrDec, + texts::arithmetic, + texts::bitArithmetic, + texts::logical, + texts::access, + texts::specialAngles, + texts::rest); + std::ranges::sort(collection); + MIMICPP_ASSERT(collection.cend() == std::ranges::unique(collection).begin(), "Fix your input!"); + + return collection; + }); + + class NameLexer + { + public: + [[nodiscard]] + explicit constexpr NameLexer(StringViewT text) noexcept + : m_Text{std::move(text)}, + m_Next{find_next()} + { + } + + [[nodiscard]] + constexpr token next() noexcept + { + return std::exchange(m_Next, find_next()); + } + + [[nodiscard]] + constexpr token const& peek() const noexcept + { + return m_Next; + } + + private: + StringViewT m_Text; + token m_Next; + + [[nodiscard]] + constexpr token find_next() noexcept + { + if (m_Text.empty()) + { + return end{}; + } + + if (is_space(m_Text.front())) + { + // Multiple consecutive spaces or any whitespace character other than a single space + // carry no meaningful semantic value beyond delimitation. + // Although single spaces may sometimes influence the result and sometimes not, + // complicating the overall process, we filter out all non-single whitespace characters here. + if (auto const token = next_as_space(); + token.content == " ") + { + return space{.content = token.content}; + } + + return find_next(); + } + + if (auto const options = util::prefix_range( + operatorOrPunctuatorCollection, + m_Text.substr(0u, 1u))) + { + auto const token = next_as_op_or_punctuator(options); + if (auto const content = token.content; + content == ",") + { + return comma{.content = content}; + } + else if (content == "::") + { + return scope_resolution{.content = content}; + } + + return token; + } + + auto const token = next_as_identifier(); + // As we do not perform any prefix-checks, we need to check now whether the token actually denotes a keyword. + if (std::ranges::binary_search(keywordCollection, token.content)) + { + return keyword{.content = token.content}; + } + + return token; + } + + [[nodiscard]] + constexpr space next_as_space() noexcept + { + auto const end = std::ranges::find_if_not(m_Text.cbegin() + 1, m_Text.cend(), is_space); + space const token{ + .content = {m_Text.cbegin(), end} + }; + m_Text = StringViewT{end, m_Text.cend()}; + + return token; + } + + /** + * \brief Extracts the next operator or punctuator. + * \details Performs longest-prefix matching. + */ + [[nodiscard]] + constexpr operator_or_punctuator next_as_op_or_punctuator(std::span options) noexcept + { + MIMICPP_ASSERT(m_Text.substr(0u, 1u) == options.front(), "Assumption does not hold."); + + auto const try_advance = [&, this](std::size_t const n) { + if (n <= m_Text.size()) + { + return util::prefix_range( + options, + StringViewT{m_Text.cbegin(), m_Text.cbegin() + n}); + } + + return std::ranges::subrange{options.end(), options.end()}; + }; + + std::size_t length{1u}; + StringViewT const* lastMatch = &options.front(); + while (auto const nextOptions = try_advance(length + 1)) + { + ++length; + options = {nextOptions.begin(), nextOptions.end()}; + + // If the first string is exactly the size of the prefix, it's a match. + if (auto const& front = options.front(); + length == front.size()) + { + lastMatch = &front; + } + } + + MIMICPP_ASSERT(!options.empty(), "Invalid state."); + MIMICPP_ASSERT(lastMatch, "Invalid state."); + + operator_or_punctuator const token{.content = m_Text.substr(0u, lastMatch->size())}; + m_Text.remove_prefix(lastMatch->size()); + + return token; + } + + /** + * \brief Extracts the next identifier. + * \details This approach differs a lot from the general c++ process. Instead of utilizing a specific alphabet + * of valid characters (and thus performing a whitelist-test), we use a more permissive approach here and check + * whether the next character is not a space and not prefix of a operator or punctuator. + * This has to be done, because demangled names may (and will!) contain various non-allowed tokens. + * + * As we make the assumption that the underlying name is actually correct, we do not need to check for validity + * here. Just treat everything else as identifier and let the parser do the rest. + */ + [[nodiscard]] + constexpr identifier next_as_identifier() noexcept + { + auto const last = std::ranges::find_if_not( + m_Text.cbegin() + 1, + m_Text.cend(), + [](auto const c) { + return !is_space(c) + && !std::ranges::binary_search(operatorOrPunctuatorCollection, StringViewT{&c, 1u}); + }); + + identifier const token{ + .content = {m_Text.cbegin(), last} + }; + m_Text = {last, m_Text.cend()}; + + return token; + } + }; +} + +#endif diff --git a/test/unit-tests/printing/CMakeLists.txt b/test/unit-tests/printing/CMakeLists.txt index d75a3e333..6f9c1cc25 100644 --- a/test/unit-tests/printing/CMakeLists.txt +++ b/test/unit-tests/printing/CMakeLists.txt @@ -13,4 +13,5 @@ target_sources(${TARGET_NAME} "TypeScopeIterator.cpp" "TypePostProcessing.cpp" "TypePrinter.cpp" + "TypeNameLexer.cpp" ) diff --git a/test/unit-tests/printing/TypeNameLexer.cpp b/test/unit-tests/printing/TypeNameLexer.cpp new file mode 100644 index 000000000..a8404b428 --- /dev/null +++ b/test/unit-tests/printing/TypeNameLexer.cpp @@ -0,0 +1,482 @@ +// Copyright Dominic (DNKpp) Koepke 2024 - 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 "mimic++/printing/type/NameLexer.hpp" + +#include "TestTypes.hpp" + +using namespace mimicpp; + +namespace +{ + template + [[nodiscard]] + constexpr auto matches_token(Value token) + { + return VariantEqualsMatcher{std::move(token)}; + } +} + +TEST_CASE( + "printing::type::lexing::NameLexer extracts tokens from given input.", + "[print][print::type]") +{ + using namespace printing::type::lexing; + + SECTION("Empty input is supported.") + { + NameLexer lexer{""}; + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Single spaces are detected.") + { + auto const expectedToken = matches_token(space{" "}); + NameLexer lexer{" "}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Multiple spaces or any non-standard space is ignored.") + { + StringViewT const input = GENERATE( + // " ", single spaces are treated specially. + "\t", + " ", + "\t\t", + "\t \t", + " \t "); + CAPTURE(input); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Comma is detected.") + { + auto const expectedToken = matches_token(comma{","}); + + NameLexer lexer{","}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Scope-resolution is detected.") + { + auto const expectedToken = matches_token(scope_resolution{"::"}); + + NameLexer lexer{"::"}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Common brace-likes are detected.") + { + StringViewT const input = GENERATE(from_range(texts::braceLikes)); + CAPTURE(input); + + auto const expectedToken = matches_token(operator_or_punctuator{input}); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Common comparison-operators are detected.") + { + StringViewT const input = GENERATE(from_range(texts::comparison)); + CAPTURE(input); + + auto const expectedToken = matches_token(operator_or_punctuator{input}); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Common assignment-operators are detected.") + { + StringViewT const input = GENERATE(from_range(texts::assignment)); + CAPTURE(input); + + auto const expectedToken = matches_token(operator_or_punctuator{input}); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Common increment- and decrement-operators are detected.") + { + StringViewT const input = GENERATE(from_range(texts::incOrDec)); + CAPTURE(input); + + auto const expectedToken = matches_token(operator_or_punctuator{input}); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Common arithmetic-operators are detected.") + { + StringViewT const input = GENERATE(from_range(texts::arithmetic)); + CAPTURE(input); + + auto const expectedToken = matches_token(operator_or_punctuator{input}); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Common bit-arithmetic-operators are detected.") + { + StringViewT const input = GENERATE(from_range(texts::bitArithmetic)); + CAPTURE(input); + + auto const expectedToken = matches_token(operator_or_punctuator{input}); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Common logical-operators are detected.") + { + StringViewT const input = GENERATE(from_range(texts::logical)); + CAPTURE(input); + + auto const expectedToken = matches_token(operator_or_punctuator{input}); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Common access-operators are detected.") + { + StringViewT const input = GENERATE(from_range(texts::access)); + CAPTURE(input); + + auto const expectedToken = matches_token(operator_or_punctuator{input}); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Common special angles are detected.") + { + StringViewT const input = GENERATE(from_range(texts::specialAngles)); + CAPTURE(input); + + auto const expectedToken = matches_token(operator_or_punctuator{input}); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Keywords are detected.") + { + StringViewT const input = GENERATE(from_range(keywordCollection)); + CAPTURE(input); + + auto const expectedToken = matches_token(keyword{input}); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Arbitrary Keywords are detected.") + { + StringViewT const input = GENERATE("foo", "_123", "foo456", "const_", "_const"); + CAPTURE(input); + + auto const expectedToken = matches_token(identifier{input}); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } +} + +TEST_CASE( + "printing::type::lexing::NameLexer supports token compositions.", + "[print][print::type]") +{ + using namespace printing::type::lexing; + + SECTION("space + identifier.") + { + constexpr StringViewT input = "\ttest"; + CAPTURE(input); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(identifier{"test"})); + + CHECK_THAT( + lexer.next(), + matches_token(identifier{"test"})); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("Operator + operator.") + { + constexpr StringViewT input = "++--"; + CAPTURE(input); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(operator_or_punctuator{"++"})); + + CHECK_THAT( + lexer.next(), + matches_token(operator_or_punctuator{"++"})); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(operator_or_punctuator{"--"})); + + CHECK_THAT( + lexer.next(), + matches_token(operator_or_punctuator{"--"})); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_token(end{})); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } + + SECTION("keyword + space + identifier + operator + operator + space + keyword + operator.") + { + constexpr StringViewT input = "const\t foo123[] volatile&&"; + CAPTURE(input); + + std::tuple const sequence = { + matches_token(keyword{"const"}), + matches_token(identifier{"foo123"}), + matches_token(operator_or_punctuator{"["}), + matches_token(operator_or_punctuator{"]"}), + matches_token(space{" "}), + matches_token(keyword{"volatile"}), + matches_token(operator_or_punctuator{"&&"})}; + + NameLexer lexer{input}; + + std::apply( + [&](auto&... matchers) { + auto check = [&, i = 0](auto const& matcher) mutable { + CAPTURE(i); + ++i; + CHECK_THAT( + std::as_const(lexer).peek(), + matcher); + CHECK_THAT( + lexer.next(), + matcher); + }; + + (check(matchers), ...); + }, + sequence); + + CHECK_THAT( + lexer.next(), + matches_token(end{})); + } +} From d779753f0ff8637c6698c4515bbafdb61be264e1 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 25 Mar 2025 22:44:29 +0100 Subject: [PATCH 004/130] chore: enhance VariantEqualsMatcher --- test/unit-tests/TestTypes.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/unit-tests/TestTypes.hpp b/test/unit-tests/TestTypes.hpp index 725e4fc43..cb017350f 100644 --- a/test/unit-tests/TestTypes.hpp +++ b/test/unit-tests/TestTypes.hpp @@ -456,9 +456,13 @@ class VariantEqualsMatcher final && m_Value == std::get(other); } + [[nodiscard]] std::string describe() const override { - return std::string{"Variant state equals: "} + Catch::Detail::stringify(m_Value); + return std::string{"Variant state equals: "} + + mimicpp::print_type() + + ": " + + Catch::Detail::stringify(m_Value); } private: From 30b0282a158af8d0b02b2143a255f3fb33c73350 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 5 Apr 2025 13:38:54 +0200 Subject: [PATCH 005/130] chore: rework NameLexer.hpp --- include/mimic++/printing/type/NameLexer.hpp | 91 ++++---- test/unit-tests/printing/TypeNameLexer.cpp | 223 +++++++++++++------- 2 files changed, 189 insertions(+), 125 deletions(-) diff --git a/include/mimic++/printing/type/NameLexer.hpp b/include/mimic++/printing/type/NameLexer.hpp index bd8122b6f..ae49fa885 100644 --- a/include/mimic++/printing/type/NameLexer.hpp +++ b/include/mimic++/printing/type/NameLexer.hpp @@ -44,8 +44,6 @@ namespace mimicpp::printing::type::lexing struct space { - StringViewT content; - [[nodiscard]] bool operator==(space const&) const = default; }; @@ -58,22 +56,6 @@ namespace mimicpp::printing::type::lexing bool operator==(keyword const&) const = default; }; - struct comma - { - StringViewT content; - - [[nodiscard]] - bool operator==(comma const&) const = default; - }; - - struct scope_resolution - { - StringViewT content; - - [[nodiscard]] - bool operator==(scope_resolution const&) const = default; - }; - struct operator_or_punctuator { StringViewT content; @@ -96,15 +78,19 @@ namespace mimicpp::printing::type::lexing bool operator==(end const&) const = default; }; - using token = std::variant< + using token_class = std::variant< end, space, keyword, - comma, - scope_resolution, operator_or_punctuator, identifier>; + struct token + { + StringViewT content; + token_class classification; + }; + namespace texts { // just list the noteworthy ones here @@ -186,7 +172,10 @@ namespace mimicpp::printing::type::lexing { if (m_Text.empty()) { - return end{}; + return token{ + .content = {m_Text.cend(), m_Text.cend()}, + .classification = end{} + }; } if (is_space(m_Text.front())) @@ -195,10 +184,12 @@ namespace mimicpp::printing::type::lexing // carry no meaningful semantic value beyond delimitation. // Although single spaces may sometimes influence the result and sometimes not, // complicating the overall process, we filter out all non-single whitespace characters here. - if (auto const token = next_as_space(); - token.content == " ") + if (StringViewT const content = next_as_space(); + " " == content) { - return space{.content = token.content}; + return token{ + .content = content, + .classification = space{}}; } return find_next(); @@ -208,40 +199,34 @@ namespace mimicpp::printing::type::lexing operatorOrPunctuatorCollection, m_Text.substr(0u, 1u))) { - auto const token = next_as_op_or_punctuator(options); - if (auto const content = token.content; - content == ",") - { - return comma{.content = content}; - } - else if (content == "::") - { - return scope_resolution{.content = content}; - } - - return token; + StringViewT const content = next_as_op_or_punctuator(options); + return token{ + .content = content, + .classification = operator_or_punctuator{.content = content}}; } - auto const token = next_as_identifier(); + StringViewT const content = next_as_identifier(); // As we do not perform any prefix-checks, we need to check now whether the token actually denotes a keyword. - if (std::ranges::binary_search(keywordCollection, token.content)) + if (std::ranges::binary_search(keywordCollection, content)) { - return keyword{.content = token.content}; + return token{ + .content = content, + .classification = keyword{.content = content}}; } - return token; + return token{ + .content = content, + .classification = identifier{.content = content}}; } [[nodiscard]] - constexpr space next_as_space() noexcept + constexpr StringViewT next_as_space() noexcept { auto const end = std::ranges::find_if_not(m_Text.cbegin() + 1, m_Text.cend(), is_space); - space const token{ - .content = {m_Text.cbegin(), end} - }; + StringViewT const content{m_Text.cbegin(), end}; m_Text = StringViewT{end, m_Text.cend()}; - return token; + return content; } /** @@ -249,7 +234,7 @@ namespace mimicpp::printing::type::lexing * \details Performs longest-prefix matching. */ [[nodiscard]] - constexpr operator_or_punctuator next_as_op_or_punctuator(std::span options) noexcept + constexpr StringViewT next_as_op_or_punctuator(std::span options) noexcept { MIMICPP_ASSERT(m_Text.substr(0u, 1u) == options.front(), "Assumption does not hold."); @@ -282,10 +267,10 @@ namespace mimicpp::printing::type::lexing MIMICPP_ASSERT(!options.empty(), "Invalid state."); MIMICPP_ASSERT(lastMatch, "Invalid state."); - operator_or_punctuator const token{.content = m_Text.substr(0u, lastMatch->size())}; + StringViewT const content{m_Text.substr(0u, lastMatch->size())}; m_Text.remove_prefix(lastMatch->size()); - return token; + return content; } /** @@ -299,7 +284,7 @@ namespace mimicpp::printing::type::lexing * here. Just treat everything else as identifier and let the parser do the rest. */ [[nodiscard]] - constexpr identifier next_as_identifier() noexcept + constexpr StringViewT next_as_identifier() noexcept { auto const last = std::ranges::find_if_not( m_Text.cbegin() + 1, @@ -309,12 +294,10 @@ namespace mimicpp::printing::type::lexing && !std::ranges::binary_search(operatorOrPunctuatorCollection, StringViewT{&c, 1u}); }); - identifier const token{ - .content = {m_Text.cbegin(), last} - }; + StringViewT const content{m_Text.cbegin(), last}; m_Text = {last, m_Text.cend()}; - return token; + return content; } }; } diff --git a/test/unit-tests/printing/TypeNameLexer.cpp b/test/unit-tests/printing/TypeNameLexer.cpp index a8404b428..8e4f858c1 100644 --- a/test/unit-tests/printing/TypeNameLexer.cpp +++ b/test/unit-tests/printing/TypeNameLexer.cpp @@ -11,11 +11,69 @@ using namespace mimicpp; namespace { - template + template + requires std::constructible_from + class TokenMatcher final + : public Catch::Matchers::MatcherGenericBase + { + public: + [[nodiscard]] + explicit constexpr TokenMatcher(TokenClass tokenClass) + : m_ClassMatcher{std::move(tokenClass)} + { + } + + [[nodiscard]] + explicit constexpr TokenMatcher(StringViewT content, TokenClass tokenClass) + : m_ClassMatcher{std::move(tokenClass)}, + m_Content{std::move(content)} + { + } + + [[nodiscard]] + constexpr bool match(printing::type::lexing::token const& token) const + { + return m_ClassMatcher.match(token.classification) + && (!m_Content || token.content == m_Content.value()); + } + + [[nodiscard]] + std::string describe() const override + { + std::string description = std::string{"Lexing-Token equals class: "} + + mimicpp::print_type(); + if (m_Content) + { + description += " and contains content: '"; + description.append(*m_Content); + description += "'"; + } + + return description; + } + + private: + VariantEqualsMatcher m_ClassMatcher; + std::optional m_Content{}; + }; + + template + [[nodiscard]] + constexpr auto matches_class(TokenClass token) + { + return TokenMatcher{std::move(token)}; + } + + template [[nodiscard]] - constexpr auto matches_token(Value token) + constexpr auto matches_token(StringViewT const& content, TokenClass token) + { + return TokenMatcher{content, std::move(token)}; + } + + constexpr auto matches_end_token() { - return VariantEqualsMatcher{std::move(token)}; + return matches_token("", printing::type::lexing::end{}); } } @@ -30,31 +88,30 @@ TEST_CASE( NameLexer lexer{""}; CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Single spaces are detected.") { - auto const expectedToken = matches_token(space{" "}); NameLexer lexer{" "}; CHECK_THAT( std::as_const(lexer).peek(), - expectedToken); + matches_token(" ", space{})); CHECK_THAT( lexer.next(), - expectedToken); + matches_token(" ", space{})); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Multiple spaces or any non-standard space is ignored.") @@ -71,14 +128,14 @@ TEST_CASE( NameLexer lexer{input}; CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } - SECTION("Comma is detected.") + /*SECTION("Comma is detected.") { auto const expectedToken = matches_token(comma{","}); @@ -92,11 +149,11 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Scope-resolution is detected.") @@ -113,19 +170,19 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); - } + matches_end_token()); + }*/ SECTION("Common brace-likes are detected.") { StringViewT const input = GENERATE(from_range(texts::braceLikes)); CAPTURE(input); - auto const expectedToken = matches_token(operator_or_punctuator{input}); + auto const expectedToken = matches_token(input, operator_or_punctuator{input}); NameLexer lexer{input}; CHECK_THAT( @@ -137,11 +194,11 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Common comparison-operators are detected.") @@ -149,7 +206,7 @@ TEST_CASE( StringViewT const input = GENERATE(from_range(texts::comparison)); CAPTURE(input); - auto const expectedToken = matches_token(operator_or_punctuator{input}); + auto const expectedToken = matches_token(input, operator_or_punctuator{input}); NameLexer lexer{input}; CHECK_THAT( @@ -161,11 +218,11 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Common assignment-operators are detected.") @@ -173,7 +230,7 @@ TEST_CASE( StringViewT const input = GENERATE(from_range(texts::assignment)); CAPTURE(input); - auto const expectedToken = matches_token(operator_or_punctuator{input}); + auto const expectedToken = matches_token(input, operator_or_punctuator{input}); NameLexer lexer{input}; CHECK_THAT( @@ -185,11 +242,11 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Common increment- and decrement-operators are detected.") @@ -197,7 +254,7 @@ TEST_CASE( StringViewT const input = GENERATE(from_range(texts::incOrDec)); CAPTURE(input); - auto const expectedToken = matches_token(operator_or_punctuator{input}); + auto const expectedToken = matches_token(input, operator_or_punctuator{input}); NameLexer lexer{input}; CHECK_THAT( @@ -209,11 +266,11 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Common arithmetic-operators are detected.") @@ -221,7 +278,7 @@ TEST_CASE( StringViewT const input = GENERATE(from_range(texts::arithmetic)); CAPTURE(input); - auto const expectedToken = matches_token(operator_or_punctuator{input}); + auto const expectedToken = matches_token(input, operator_or_punctuator{input}); NameLexer lexer{input}; CHECK_THAT( @@ -233,11 +290,11 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Common bit-arithmetic-operators are detected.") @@ -245,7 +302,7 @@ TEST_CASE( StringViewT const input = GENERATE(from_range(texts::bitArithmetic)); CAPTURE(input); - auto const expectedToken = matches_token(operator_or_punctuator{input}); + auto const expectedToken = matches_token(input, operator_or_punctuator{input}); NameLexer lexer{input}; CHECK_THAT( @@ -257,11 +314,11 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Common logical-operators are detected.") @@ -269,7 +326,7 @@ TEST_CASE( StringViewT const input = GENERATE(from_range(texts::logical)); CAPTURE(input); - auto const expectedToken = matches_token(operator_or_punctuator{input}); + auto const expectedToken = matches_token(input, operator_or_punctuator{input}); NameLexer lexer{input}; CHECK_THAT( @@ -281,11 +338,11 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Common access-operators are detected.") @@ -293,7 +350,7 @@ TEST_CASE( StringViewT const input = GENERATE(from_range(texts::access)); CAPTURE(input); - auto const expectedToken = matches_token(operator_or_punctuator{input}); + auto const expectedToken = matches_token(input, operator_or_punctuator{input}); NameLexer lexer{input}; CHECK_THAT( @@ -305,11 +362,11 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Common special angles are detected.") @@ -317,7 +374,31 @@ TEST_CASE( StringViewT const input = GENERATE(from_range(texts::specialAngles)); CAPTURE(input); - auto const expectedToken = matches_token(operator_or_punctuator{input}); + auto const expectedToken = matches_token(input, operator_or_punctuator{input}); + + NameLexer lexer{input}; + CHECK_THAT( + std::as_const(lexer).peek(), + expectedToken); + + CHECK_THAT( + lexer.next(), + expectedToken); + CHECK_THAT( + std::as_const(lexer).peek(), + matches_end_token()); + + CHECK_THAT( + lexer.next(), + matches_end_token()); + } + + SECTION("All other operators or punctuators are detected.") + { + StringViewT const input = GENERATE(from_range(texts::rest)); + CAPTURE(input); + + auto const expectedToken = matches_token(input, operator_or_punctuator{input}); NameLexer lexer{input}; CHECK_THAT( @@ -329,11 +410,11 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Keywords are detected.") @@ -341,7 +422,7 @@ TEST_CASE( StringViewT const input = GENERATE(from_range(keywordCollection)); CAPTURE(input); - auto const expectedToken = matches_token(keyword{input}); + auto const expectedToken = matches_token(input, keyword{input}); NameLexer lexer{input}; CHECK_THAT( @@ -353,19 +434,19 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } - SECTION("Arbitrary Keywords are detected.") + SECTION("Arbitrary identifiers are detected.") { StringViewT const input = GENERATE("foo", "_123", "foo456", "const_", "_const"); CAPTURE(input); - auto const expectedToken = matches_token(identifier{input}); + auto const expectedToken = matches_token(input, identifier{input}); NameLexer lexer{input}; CHECK_THAT( @@ -377,11 +458,11 @@ TEST_CASE( expectedToken); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } } @@ -391,7 +472,7 @@ TEST_CASE( { using namespace printing::type::lexing; - SECTION("space + identifier.") + SECTION("tab + identifier.") { constexpr StringViewT input = "\ttest"; CAPTURE(input); @@ -399,18 +480,18 @@ TEST_CASE( NameLexer lexer{input}; CHECK_THAT( std::as_const(lexer).peek(), - matches_token(identifier{"test"})); + matches_class(identifier{"test"})); CHECK_THAT( lexer.next(), - matches_token(identifier{"test"})); + matches_class(identifier{"test"})); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("Operator + operator.") @@ -421,25 +502,25 @@ TEST_CASE( NameLexer lexer{input}; CHECK_THAT( std::as_const(lexer).peek(), - matches_token(operator_or_punctuator{"++"})); + matches_class(operator_or_punctuator{"++"})); CHECK_THAT( lexer.next(), - matches_token(operator_or_punctuator{"++"})); + matches_class(operator_or_punctuator{"++"})); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(operator_or_punctuator{"--"})); + matches_class(operator_or_punctuator{"--"})); CHECK_THAT( lexer.next(), - matches_token(operator_or_punctuator{"--"})); + matches_class(operator_or_punctuator{"--"})); CHECK_THAT( std::as_const(lexer).peek(), - matches_token(end{})); + matches_end_token()); CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } SECTION("keyword + space + identifier + operator + operator + space + keyword + operator.") @@ -448,13 +529,13 @@ TEST_CASE( CAPTURE(input); std::tuple const sequence = { - matches_token(keyword{"const"}), - matches_token(identifier{"foo123"}), - matches_token(operator_or_punctuator{"["}), - matches_token(operator_or_punctuator{"]"}), - matches_token(space{" "}), - matches_token(keyword{"volatile"}), - matches_token(operator_or_punctuator{"&&"})}; + matches_class(keyword{"const"}), + matches_class(identifier{"foo123"}), + matches_class(operator_or_punctuator{"["}), + matches_class(operator_or_punctuator{"]"}), + matches_class(space{}), + matches_class(keyword{"volatile"}), + matches_class(operator_or_punctuator{"&&"})}; NameLexer lexer{input}; @@ -477,6 +558,6 @@ TEST_CASE( CHECK_THAT( lexer.next(), - matches_token(end{})); + matches_end_token()); } } From 5dea60dee8540762dbab8b6c83b32a592c735b7f Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 5 Apr 2025 14:39:15 +0200 Subject: [PATCH 006/130] feat: util::projected_value_t c++26-backport --- include/mimic++/utilities/C++26Backports.hpp | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 include/mimic++/utilities/C++26Backports.hpp diff --git a/include/mimic++/utilities/C++26Backports.hpp b/include/mimic++/utilities/C++26Backports.hpp new file mode 100644 index 000000000..46490855e --- /dev/null +++ b/include/mimic++/utilities/C++26Backports.hpp @@ -0,0 +1,33 @@ +// Copyright Dominic (DNKpp) Koepke 2024 - 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 MIMICPP_UTILITIES_CXX26_BACKPORTS_HPP +#define MIMICPP_UTILITIES_CXX26_BACKPORTS_HPP + +#pragma once + +#include "mimic++/config/Config.hpp" + +#include +#include +#include +#include + +namespace mimicpp::util +{ + /** + * \brief The alias template projected_value_t obtains the value type by stripping any reference and its topmost + * cv-qualifiers of the result type of applying Proj to `std::iter_value_t&`. + * \tparam I an indirectly readable type. + * \tparam Projection projection applied to an lvalue reference to value type of `I`. + * \see https://en.cppreference.com/w/cpp/iterator/projected_value_t + * \note Implementation directly taken from https://en.cppreference.com/w/cpp/iterator/projected_value_t + */ + template Projection> + using projected_value_t = std::remove_cvref_t< + std::invoke_result_t&>>; +} + +#endif From 1f8e6fb0e37dad3c2a102574c3c4509813bfa108 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 5 Apr 2025 14:57:23 +0200 Subject: [PATCH 007/130] fix: add utilities/C++26Backports.hpp to Utilities.hpp --- include/mimic++/Utilities.hpp | 1 + include/mimic++/utilities/Algorithm.hpp | 63 ++++++++++++++++++ test/unit-tests/utilities/Algorithm.cpp | 85 ++++++++++++++++++++++--- 3 files changed, 139 insertions(+), 10 deletions(-) diff --git a/include/mimic++/Utilities.hpp b/include/mimic++/Utilities.hpp index 8b5109bfa..3a9b73b1a 100644 --- a/include/mimic++/Utilities.hpp +++ b/include/mimic++/Utilities.hpp @@ -9,6 +9,7 @@ #include "mimic++/utilities/Algorithm.hpp" #include "mimic++/utilities/AlwaysFalse.hpp" #include "mimic++/utilities/C++23Backports.hpp" +#include "mimic++/utilities/C++26Backports.hpp" #include "mimic++/utilities/Concepts.hpp" #include "mimic++/utilities/PriorityTag.hpp" #include "mimic++/utilities/SourceLocation.hpp" diff --git a/include/mimic++/utilities/Algorithm.hpp b/include/mimic++/utilities/Algorithm.hpp index fb253874c..3f0eac158 100644 --- a/include/mimic++/utilities/Algorithm.hpp +++ b/include/mimic++/utilities/Algorithm.hpp @@ -9,6 +9,7 @@ #pragma once #include "mimic++/config/Config.hpp" +#include "mimic++/utilities/C++26Backports.hpp" #include #include @@ -250,6 +251,68 @@ namespace mimicpp::util concat_arrays(others...)); } } + + namespace detail + { + struct binary_find_fn + { + template < + std::forward_iterator Iterator, + std::sentinel_for Sentinel, + typename Projection = std::identity, + typename T = util::projected_value_t, + std::indirect_strict_weak_order< + T const*, + std::projected> Comparator = std::ranges::less> + [[nodiscard]] + constexpr Iterator operator()( + Iterator const first, + Sentinel const last, + T const& value, + Comparator compare = {}, + Projection projection = {}) const + { + if (auto const iter = std::ranges::lower_bound(first, last, value, compare, projection); + iter != last + && !std::invoke(compare, value, std::invoke(projection, *iter))) + { + return iter; + } + + return last; + } + + template < + std::ranges::forward_range Range, + typename Projection = std::identity, + typename T = util::projected_value_t, Projection>, + std::indirect_strict_weak_order< + T const*, + std::projected, Projection>> Comparator = std::ranges::less> + [[nodiscard]] + constexpr std::ranges::borrowed_iterator_t operator()( + Range&& range, + T const& value, + Comparator compare = {}, + Projection projection = {}) const + { + return std::invoke( + *this, + std::ranges::begin(range), + std::ranges::end(range), + value, + std::move(compare), + std::move(projection)); + } + }; + } + + /** + * \brief Finds the specified value within the container and returns an iterator pointing to it. + * If the value is not found, it returns an iterator to the end of the container. + * \return A borrowed iterator to the element (or end). + */ + inline constexpr detail::binary_find_fn binary_find{}; } #endif diff --git a/test/unit-tests/utilities/Algorithm.cpp b/test/unit-tests/utilities/Algorithm.cpp index 1f191aca3..a00df2a91 100644 --- a/test/unit-tests/utilities/Algorithm.cpp +++ b/test/unit-tests/utilities/Algorithm.cpp @@ -9,7 +9,7 @@ using namespace mimicpp; TEST_CASE( "util::partition_by on empty ranges has no effect.", - "[util][util::partition_by]") + "[util][util::algorithm]") { std::vector target{}; std::vector control{}; @@ -28,7 +28,7 @@ TEST_CASE( TEST_CASE( "util::partition_by supports ranges with just one element.", - "[util][util::partition_by]") + "[util][util::algorithm]") { std::vector target{"Hello, World!"}; std::vector control{42}; @@ -59,7 +59,7 @@ TEST_CASE( TEST_CASE( "util::partition_by supports ranges with multiple elements.", - "[util][util::partition_by]") + "[util][util::algorithm]") { std::vector target{"Test1", "Test2", "Test3"}; std::vector control{1, 2, 3}; @@ -116,7 +116,7 @@ TEST_CASE( TEST_CASE( "util::find_closing_token returns end iterator, when no corresponding closing-token can be found.", - "[util][util::find_closing_token]") + "[util][util::algorithm]") { SECTION("When string is empty.") { @@ -162,7 +162,7 @@ TEST_CASE( TEST_CASE( "util::find_closing_token returns the iterator to the closing-token.", - "[util][util::find_closing_token]") + "[util][util::algorithm]") { auto [expectedIndex, str] = GENERATE( (table)({ @@ -181,7 +181,7 @@ TEST_CASE( TEST_CASE( "util::find_next_unwrapped_token returns a subrange to the next token.", - "[util][util::find_next_unwrapped_token]") + "[util][util::algorithm]") { constexpr std::array openingBrackets{'<', '(', '[', '{'}; constexpr std::array closingBrackets{'>', ')', ']', '}'}; @@ -203,7 +203,7 @@ TEST_CASE( TEST_CASE( "util::find_next_unwrapped_token can handle tokens, which are part of either collection.", - "[util][util::find_next_unwrapped_token]") + "[util][util::algorithm]") { constexpr std::array openingBrackets{'<', '(', '[', '{'}; constexpr std::array closingBrackets{'>', ')', ']', '}'}; @@ -229,7 +229,7 @@ TEST_CASE( TEST_CASE( "util::find_next_unwrapped_token returns {end, end}, if no next unwrapped token exists.", - "[util][util::find_next_unwrapped_token]") + "[util][util::algorithm]") { constexpr std::array openingBrackets{'<', '(', '[', '{'}; constexpr std::array closingBrackets{'>', ')', ']', '}'}; @@ -251,7 +251,7 @@ TEST_CASE( TEST_CASE( "util::prefix_range returns the subrange to the elements, which have the given prefix.", - "[util][util::prefix_range]") + "[util][util::algorithm]") { SECTION("Empty collection is supported.") { @@ -302,7 +302,7 @@ TEST_CASE( TEST_CASE( "util::concat_arrays concatenates an arbitrary amount of arrays.", - "[util][util::concat_arrays]") + "[util][util::algorithm]") { SECTION("Single array is supported.") { @@ -354,3 +354,68 @@ TEST_CASE( Catch::Matchers::IsEmpty()); } } + +TEST_CASE( + "util::binary_find finds the required element in the container.", + "[util][util::algorithm]") +{ + SECTION("When container contains just a single element.") + { + std::vector const collection = {42}; + + auto const result = util::binary_find(collection, 42); + + CHECK(result == collection.cbegin()); + } + + SECTION("When value is first element.") + { + std::vector const collection = {42, 1337, 1338}; + + auto const result = util::binary_find(collection, 42); + + CHECK(result == collection.cbegin()); + } + + SECTION("When value is last element.") + { + std::vector const collection = {42, 1337, 1338}; + + auto const result = util::binary_find(collection, 1338); + + CHECK(result == collection.cbegin() + 2); + } + + SECTION("When value is somewhere in the middle.") + { + std::vector const collection = {42, 1337, 1338}; + + auto const result = util::binary_find(collection, 1337); + + CHECK(result == collection.cbegin() + 1); + } +} + +TEST_CASE( + "util::binary_find returns end-iterator, when element is not contained.", + "[util][util::algorithm]") +{ + SECTION("When container is empty.") + { + std::vector const collection{}; + + auto const result = util::binary_find(collection, 42); + + CHECK(result == collection.cend()); + } + + SECTION("When container is not empty, but value is not contained.") + { + std::vector const collection = {42, 1337, 1338}; + auto const value = GENERATE(-1, 0, 43, 1336, 1339); + + auto const result = util::binary_find(collection, value); + + CHECK(result == collection.cend()); + } +} From 0ea426b9238d218fc2625b194c9e9216a78ecc83 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 5 Apr 2025 17:46:10 +0200 Subject: [PATCH 008/130] fix: NameLexer handles << and >> correctly --- include/mimic++/printing/type/NameLexer.hpp | 157 ++++++++++++-------- 1 file changed, 99 insertions(+), 58 deletions(-) diff --git a/include/mimic++/printing/type/NameLexer.hpp b/include/mimic++/printing/type/NameLexer.hpp index ae49fa885..34827dd83 100644 --- a/include/mimic++/printing/type/NameLexer.hpp +++ b/include/mimic++/printing/type/NameLexer.hpp @@ -42,55 +42,6 @@ namespace mimicpp::printing::type::lexing return static_cast(std::isxdigit(static_cast(c))); }; - struct space - { - [[nodiscard]] - bool operator==(space const&) const = default; - }; - - struct keyword - { - StringViewT content; - - [[nodiscard]] - bool operator==(keyword const&) const = default; - }; - - struct operator_or_punctuator - { - StringViewT content; - - [[nodiscard]] - bool operator==(operator_or_punctuator const&) const = default; - }; - - struct identifier - { - StringViewT content; - - [[nodiscard]] - bool operator==(identifier const&) const = default; - }; - - struct end - { - [[nodiscard]] - bool operator==(end const&) const = default; - }; - - using token_class = std::variant< - end, - space, - keyword, - operator_or_punctuator, - identifier>; - - struct token - { - StringViewT content; - token_class classification; - }; - namespace texts { // just list the noteworthy ones here @@ -102,7 +53,7 @@ namespace mimicpp::printing::type::lexing constexpr std::array assignment = std::to_array({"=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>="}); constexpr std::array incOrDec = std::to_array({"++", "--"}); constexpr std::array arithmetic = std::to_array({"+", "-", "*", "/", "%"}); - constexpr std::array bitArithmetic = std::to_array({"~", "&", "|", "^"}); + constexpr std::array bitArithmetic = std::to_array({"~", "&", "|", "^", "<<", ">>"}); constexpr std::array logical = std::to_array({"!", "&&", "||"}); constexpr std::array access = std::to_array({".", ".*", "->", "->*"}); constexpr std::array specialAngles = std::to_array({"<:", ":>", "<%", "%>"}); @@ -141,6 +92,95 @@ namespace mimicpp::printing::type::lexing return collection; }); + struct space + { + [[nodiscard]] + bool operator==(space const&) const = default; + }; + + struct keyword + { + public: + static constexpr auto& textCollection = keywordCollection; + + [[nodiscard]] + explicit constexpr keyword(StringViewT const& text) noexcept + : keyword{ + std::ranges::distance( + textCollection.cbegin(), + util::binary_find(textCollection, text))} + { + } + + [[nodiscard]] + explicit constexpr keyword(std::ptrdiff_t const keywordIndex) noexcept + : m_KeywordIndex{keywordIndex} + { + MIMICPP_ASSERT(0 <= m_KeywordIndex && m_KeywordIndex < std::ranges::ssize(textCollection), "Invalid keyword."); + } + + [[nodiscard]] + bool operator==(keyword const&) const = default; + + private: + std::ptrdiff_t m_KeywordIndex; + }; + + struct operator_or_punctuator + { + public: + static constexpr auto& textCollection = operatorOrPunctuatorCollection; + + [[nodiscard]] + explicit constexpr operator_or_punctuator(StringViewT const& text) noexcept + : operator_or_punctuator{ + std::ranges::distance( + textCollection.cbegin(), + util::binary_find(textCollection, text))} + { + } + + [[nodiscard]] + explicit constexpr operator_or_punctuator(std::ptrdiff_t const textIndex) noexcept + : m_TextIndex{textIndex} + { + MIMICPP_ASSERT(0 <= m_TextIndex && m_TextIndex < std::ranges::ssize(textCollection), "Invalid operator or punctuator."); + } + + [[nodiscard]] + bool operator==(operator_or_punctuator const&) const = default; + + private: + std::ptrdiff_t m_TextIndex; + }; + + struct identifier + { + StringViewT content; + + [[nodiscard]] + bool operator==(identifier const&) const = default; + }; + + struct end + { + [[nodiscard]] + bool operator==(end const&) const = default; + }; + + using token_class = std::variant< + end, + space, + keyword, + operator_or_punctuator, + identifier>; + + struct token + { + StringViewT content; + token_class classification; + }; + class NameLexer { public: @@ -199,19 +239,17 @@ namespace mimicpp::printing::type::lexing operatorOrPunctuatorCollection, m_Text.substr(0u, 1u))) { - StringViewT const content = next_as_op_or_punctuator(options); - return token{ - .content = content, - .classification = operator_or_punctuator{.content = content}}; + return next_as_op_or_punctuator(options); } StringViewT const content = next_as_identifier(); // As we do not perform any prefix-checks, we need to check now whether the token actually denotes a keyword. - if (std::ranges::binary_search(keywordCollection, content)) + if (auto const iter = util::binary_find(keywordCollection, content); + iter != keywordCollection.cend()) { return token{ .content = content, - .classification = keyword{.content = content}}; + .classification = keyword{std::ranges::distance(keywordCollection.begin(), iter)}}; } return token{ @@ -234,7 +272,7 @@ namespace mimicpp::printing::type::lexing * \details Performs longest-prefix matching. */ [[nodiscard]] - constexpr StringViewT next_as_op_or_punctuator(std::span options) noexcept + constexpr token next_as_op_or_punctuator(std::span options) noexcept { MIMICPP_ASSERT(m_Text.substr(0u, 1u) == options.front(), "Assumption does not hold."); @@ -267,10 +305,13 @@ namespace mimicpp::printing::type::lexing MIMICPP_ASSERT(!options.empty(), "Invalid state."); MIMICPP_ASSERT(lastMatch, "Invalid state."); + auto const index = std::ranges::distance(operatorOrPunctuatorCollection.data(), lastMatch); StringViewT const content{m_Text.substr(0u, lastMatch->size())}; m_Text.remove_prefix(lastMatch->size()); - return content; + return token{ + .content = content, + .classification = operator_or_punctuator{index}}; } /** From 1d34b38c55e02b3767136fdae4f26be2576aae1a Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 8 Apr 2025 22:32:01 +0200 Subject: [PATCH 009/130] feat: util::contains --- include/mimic++/utilities/Algorithm.hpp | 50 +++++++++++++++++++++++++ test/unit-tests/utilities/Algorithm.cpp | 32 ++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/include/mimic++/utilities/Algorithm.hpp b/include/mimic++/utilities/Algorithm.hpp index 3f0eac158..6c76ff088 100644 --- a/include/mimic++/utilities/Algorithm.hpp +++ b/include/mimic++/utilities/Algorithm.hpp @@ -313,6 +313,56 @@ namespace mimicpp::util * \return A borrowed iterator to the element (or end). */ inline constexpr detail::binary_find_fn binary_find{}; + + 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/test/unit-tests/utilities/Algorithm.cpp b/test/unit-tests/utilities/Algorithm.cpp index a00df2a91..971511ab7 100644 --- a/test/unit-tests/utilities/Algorithm.cpp +++ b/test/unit-tests/utilities/Algorithm.cpp @@ -419,3 +419,35 @@ TEST_CASE( CHECK(result == collection.cend()); } } + +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 7ac7f15f6d3ed76d60c3fedc292d53eb0c4a2e02 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 8 Apr 2025 22:39:53 +0200 Subject: [PATCH 010/130] feat: lexing::keyword::text and lexing::operator_or_punctuator::text --- include/mimic++/printing/type/NameLexer.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/mimic++/printing/type/NameLexer.hpp b/include/mimic++/printing/type/NameLexer.hpp index 34827dd83..1b61a571f 100644 --- a/include/mimic++/printing/type/NameLexer.hpp +++ b/include/mimic++/printing/type/NameLexer.hpp @@ -119,6 +119,12 @@ namespace mimicpp::printing::type::lexing MIMICPP_ASSERT(0 <= m_KeywordIndex && m_KeywordIndex < std::ranges::ssize(textCollection), "Invalid keyword."); } + [[nodiscard]] + constexpr StringViewT text() const noexcept + { + return textCollection[m_KeywordIndex]; + } + [[nodiscard]] bool operator==(keyword const&) const = default; @@ -147,6 +153,12 @@ namespace mimicpp::printing::type::lexing MIMICPP_ASSERT(0 <= m_TextIndex && m_TextIndex < std::ranges::ssize(textCollection), "Invalid operator or punctuator."); } + [[nodiscard]] + constexpr StringViewT text() const noexcept + { + return textCollection[m_TextIndex]; + } + [[nodiscard]] bool operator==(operator_or_punctuator const&) const = default; From 34825f158f85e4fc1d257ba4a8d26b6f848797db Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 14 Apr 2025 15:17:11 +0200 Subject: [PATCH 011/130] fix: remove invalid constexpr specification --- test/unit-tests/printing/TypeNameLexer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit-tests/printing/TypeNameLexer.cpp b/test/unit-tests/printing/TypeNameLexer.cpp index 8e4f858c1..791be7b6f 100644 --- a/test/unit-tests/printing/TypeNameLexer.cpp +++ b/test/unit-tests/printing/TypeNameLexer.cpp @@ -71,7 +71,8 @@ namespace return TokenMatcher{content, std::move(token)}; } - constexpr auto matches_end_token() + [[nodiscard]] + auto matches_end_token() { return matches_token("", printing::type::lexing::end{}); } From 7615c2ab243014070f019b2e0d906e068310a681 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 14 Apr 2025 15:19:53 +0200 Subject: [PATCH 012/130] feat: printing::type::parsing::NameParser, which handles identifiers --- include/mimic++/printing/type/NameParser.hpp | 327 +++++++++++++++++++ test/unit-tests/printing/CMakeLists.txt | 1 + test/unit-tests/printing/TypeNameParser.cpp | 102 ++++++ 3 files changed, 430 insertions(+) create mode 100644 include/mimic++/printing/type/NameParser.hpp create mode 100644 test/unit-tests/printing/TypeNameParser.cpp diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp new file mode 100644 index 000000000..aea198f34 --- /dev/null +++ b/include/mimic++/printing/type/NameParser.hpp @@ -0,0 +1,327 @@ +// Copyright Dominic (DNKpp) Koepke 2024 - 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 MIMICPP_PRINTING_TYPE_NAME_PARSER_HPP +#define MIMICPP_PRINTING_TYPE_NAME_PARSER_HPP + +#pragma once + +#include "mimic++/Fwd.hpp" +#include "mimic++/config/Config.hpp" +#include "mimic++/printing/type/NameLexer.hpp" +#include "mimic++/utilities/Concepts.hpp" +#include "mimic++/utilities/TypeList.hpp" + +#include +#include +#include + +namespace mimicpp::printing::type::parsing +{ + template + concept parser_visitor = std::movable + && requires(std::unwrap_reference_t visitor, StringViewT content) { + visitor.begin(); + visitor.end(); + + visitor.add_identifier(content); + visitor.add_scope(); + + visitor.begin_name(); + visitor.end_name(); + + visitor.begin_type(); + visitor.end_type(); + }; + + template + [[nodiscard]] + constexpr auto& unwrap_visitor(Visitor& visitor) noexcept + { + return static_cast< + std::add_lvalue_reference_t< + std::unwrap_reference_t>>(visitor); + } + + namespace token + { + class Identifier + { + public: + StringViewT content{}; + + template + void operator()(Visitor& visitor) const + { + MIMICPP_ASSERT(!content.empty(), "Empty identifier is not allowed."); + + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.add_identifier(content); + } + }; + + class ScopeSequence + { + public: + std::vector scopes{}; + + template + void operator()(Visitor& visitor) const + { + MIMICPP_ASSERT(!scopes.empty(), "Empty scope-sequence is not allowed."); + + auto& unwrapped = unwrap_visitor(visitor); + + for (auto const& id : scopes) + { + std::invoke(id, unwrapped); + unwrapped.add_scope(); + } + } + }; + + class Type + { + public: + std::optional scopes{}; + Identifier identifier; + + template + void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_type(); + unwrapped.begin_name(); + + if (scopes) + { + std::invoke(*scopes, unwrapped); + } + + std::invoke(identifier, unwrapped); + + unwrapped.end_name(); + unwrapped.end_type(); + } + }; + } + + using Token = std::variant< + token::Identifier, + token::ScopeSequence, + token::Type>; + using TokenStack = std::vector; + + template + concept token_type = requires(Token const& token) { + { std::holds_alternative(token) } -> util::boolean_testable; + }; + + namespace detail + { + template + [[nodiscard]] + constexpr bool is_suffix_of( + [[maybe_unused]] util::type_list const types, + std::span const tokenStack) + { + 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)); + } + + return true; + } + } + + template + constexpr bool is_suffix_of(std::span const tokenStack) + { + using types = util::type_list; + + return 1u + sizeof...(Others) <= tokenStack.size() + && detail::is_suffix_of(util::type_list_reverse_t{}, tokenStack); + } + + template + [[nodiscard]] + constexpr std::optional> match_suffix(std::span const tokenStack) + { + if (is_suffix_of(tokenStack)) + { + auto const suffix = tokenStack.last(1u + sizeof...(Others)); + + std::size_t i{0u}; + return std::tie( + std::get(suffix[0u]), + std::get(suffix[++i])...); + } + + return std::nullopt; + } + + namespace token + { + inline bool try_reduce_as_scope_sequence(TokenStack& tokenStack) + { + if (std::optional suffix = match_suffix(tokenStack)) + { + auto& [scopeSeq, id] = *suffix; + + scopeSeq.scopes.emplace_back(std::move(id)); + tokenStack.pop_back(); + + return true; + } + + if (std::optional suffix = match_suffix(tokenStack)) + { + ScopeSequence seq{ + .scopes = {std::move(std::get<0>(*suffix))}}; + tokenStack.pop_back(); + tokenStack.emplace_back(std::move(seq)); + + return true; + } + + return false; + } + + inline bool try_reduce_as_type(TokenStack& tokenStack) + { + if (std::optional suffix = match_suffix(tokenStack)) + { + auto& [scopeSeq, id] = *suffix; + + Type newType{ + .scopes = std::move(scopeSeq), + .identifier = std::move(id)}; + tokenStack.resize(tokenStack.size() - 2u); + tokenStack.emplace_back(std::move(newType)); + + return true; + } + + if (std::optional suffix = match_suffix(tokenStack)) + { + Type newType{ + .identifier = {std::move(std::get<0>(*suffix))}}; + tokenStack.pop_back(); + tokenStack.emplace_back(std::move(newType)); + + return true; + } + + return false; + } + } + + template + class NameParser + { + public: + [[nodiscard]] + explicit NameParser(Visitor visitor, StringViewT content) noexcept(std::is_nothrow_move_constructible_v) + : m_Visitor{std::move(visitor)}, + m_Lexer{std::move(content)} + { + } + + void operator()() + { + visitor().begin(); + + for (lexing::token next = m_Lexer.next(); + !std::holds_alternative(next.classification); + next = m_Lexer.next()) + { + std::visit( + [&](auto const& tokenClass) { handle_lexer_token(tokenClass); }, + next.classification); + } + + try_reduce_as_type(m_TokenStack); + + MIMICPP_ASSERT(1u == m_TokenStack.size(), "A single end-state is required."); + MIMICPP_ASSERT(std::holds_alternative(m_TokenStack.back()), "Only token::Type is allowed as end-state."); + std::invoke( + std::get(m_TokenStack.back()), + visitor()); + + visitor().end(); + } + + private: + static constexpr lexing::operator_or_punctuator openingParens{"("}; + static constexpr lexing::operator_or_punctuator closingParens{")"}; + static constexpr lexing::operator_or_punctuator openingAngle{"<"}; + static constexpr lexing::operator_or_punctuator closingAngle{">"}; + static constexpr lexing::operator_or_punctuator openingCurly{"{"}; + static constexpr lexing::operator_or_punctuator closingCurly{"}"}; + static constexpr lexing::operator_or_punctuator openingSquare{"["}; + static constexpr lexing::operator_or_punctuator closingSquare{"]"}; + static constexpr lexing::operator_or_punctuator backtick{"`"}; + static constexpr lexing::operator_or_punctuator singleQuote{"'"}; + static constexpr lexing::operator_or_punctuator scopeResolution{"::"}; + static constexpr lexing::operator_or_punctuator commaSeparator{","}; + static constexpr lexing::operator_or_punctuator pointer{"*"}; + static constexpr lexing::operator_or_punctuator lvalueRef{"&"}; + static constexpr lexing::operator_or_punctuator rvalueRef{"&&"}; + static constexpr lexing::keyword operatorKeyword{"operator"}; + static constexpr lexing::keyword constKeyword{"const"}; + static constexpr lexing::keyword volatileKeyword{"volatile"}; + static constexpr lexing::keyword noexceptKeyword{"noexcept"}; + + Visitor m_Visitor; + lexing::NameLexer m_Lexer; + + std::vector m_TokenStack{}; + + [[nodiscard]] + constexpr auto& visitor() noexcept + { + return unwrap_visitor(m_Visitor); + } + + constexpr void handle_lexer_token([[maybe_unused]] lexing::end const& end) + { + util::unreachable(); + } + + constexpr void handle_lexer_token([[maybe_unused]] lexing::space const& space) + { + } + + constexpr void handle_lexer_token(lexing::identifier const& identifier) + { + m_TokenStack.emplace_back( + token::Identifier{.content = identifier.content}); + } + + constexpr void handle_lexer_token([[maybe_unused]] lexing::keyword const& keyword) + { + } + + constexpr void handle_lexer_token(lexing::operator_or_punctuator const& token) + { + if (scopeResolution == token) + { + token::try_reduce_as_scope_sequence(m_TokenStack); + } + } + }; +} + +#endif diff --git a/test/unit-tests/printing/CMakeLists.txt b/test/unit-tests/printing/CMakeLists.txt index 6f9c1cc25..a08cac0e5 100644 --- a/test/unit-tests/printing/CMakeLists.txt +++ b/test/unit-tests/printing/CMakeLists.txt @@ -14,4 +14,5 @@ target_sources(${TARGET_NAME} "TypePostProcessing.cpp" "TypePrinter.cpp" "TypeNameLexer.cpp" + "TypeNameParser.cpp" ) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp new file mode 100644 index 000000000..c02ba4d6b --- /dev/null +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -0,0 +1,102 @@ +// Copyright Dominic (DNKpp) Koepke 2024 - 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 "mimic++/Mock.hpp" +#include "mimic++/ScopedSequence.hpp" +#include "mimic++/printing/type/NameParser.hpp" + +using namespace mimicpp; + +namespace +{ + struct VisitorMock + { + Mock begin{{.name = "VisitorMock::begin"}}; + Mock end{{.name = "VisitorMock::end"}}; + + Mock add_identifier{{.name = "VisitorMock::add_identifier"}}; + Mock add_scope{{.name = "VisitorMock::add_scope"}}; + + Mock begin_name{{.name = "VisitorMock::begin_name"}}; + Mock end_name{{.name = "VisitorMock::end_name"}}; + + Mock begin_type{{.name = "VisitorMock::begin_type"}}; + Mock end_type{{.name = "VisitorMock::end_type"}}; + }; +} + +TEST_CASE( + "parsing::NameParser detects identifiers.", + "[print][print::type]") +{ + static constexpr std::array identifiers = std::to_array({"foo", "_123", "foo456", "const_", "_const"}); + + VisitorMock visitor{}; + ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("When single identifier is given.") + { + StringViewT const input = GENERATE(from_range(identifiers)); + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_name.expect_call(); + sequence += visitor.add_identifier.expect_call(input); + sequence += visitor.end_name.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When multiple scopes are given.") + { + StringViewT const firstScope = GENERATE(from_range(identifiers)); + StringViewT const secondScope = GENERATE(from_range(identifiers)); + StringViewT const thirdScope = GENERATE(from_range(identifiers)); + + StringT const input = StringT{firstScope} + "::" + StringT{secondScope} + "::" + StringT{thirdScope}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_name.expect_call(); + + sequence += visitor.add_identifier.expect_call(firstScope); + sequence += visitor.add_scope.expect_call(); + sequence += visitor.add_identifier.expect_call(secondScope); + sequence += visitor.add_scope.expect_call(); + sequence += visitor.add_identifier.expect_call(thirdScope); + + sequence += visitor.end_name.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When root scope is explicitly given.") + { + StringViewT const scope = GENERATE(from_range(identifiers)); + StringT const input = "::" + StringT{scope}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_name.expect_call(); + sequence += visitor.add_identifier.expect_call(scope); + sequence += visitor.end_name.expect_call(); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } +} From 01235b82bcce6dcc54558feea976abe70c7560b4 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 14 Apr 2025 15:49:36 +0200 Subject: [PATCH 013/130] refactor: enhance usability of match_suffix with single suffix-element --- include/mimic++/printing/type/NameParser.hpp | 42 +++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index aea198f34..81cc8363d 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -147,7 +147,7 @@ namespace mimicpp::printing::type::parsing } template - constexpr bool is_suffix_of(std::span const tokenStack) + constexpr bool is_suffix_of(std::span const tokenStack) noexcept { using types = util::type_list; @@ -155,21 +155,35 @@ namespace mimicpp::printing::type::parsing && detail::is_suffix_of(util::type_list_reverse_t{}, tokenStack); } - template + template [[nodiscard]] - constexpr std::optional> match_suffix(std::span const tokenStack) + constexpr auto match_suffix(std::span const tokenStack) noexcept { - if (is_suffix_of(tokenStack)) + if constexpr (0u == sizeof...(Others)) { - auto const suffix = tokenStack.last(1u + sizeof...(Others)); + Leading* result{}; + if (is_suffix_of(tokenStack)) + { + result = &std::get(tokenStack.back()); + } - std::size_t i{0u}; - return std::tie( - std::get(suffix[0u]), - std::get(suffix[++i])...); + return result; } + else + { + std::optional> result{}; + if (is_suffix_of(tokenStack)) + { + auto const suffix = tokenStack.last(1u + sizeof...(Others)); + + std::size_t i{0u}; + result = std::tie( + std::get(suffix[0u]), + std::get(suffix[++i])...); + } - return std::nullopt; + return result; + } } namespace token @@ -186,10 +200,10 @@ namespace mimicpp::printing::type::parsing return true; } - if (std::optional suffix = match_suffix(tokenStack)) + if (auto* identifier = match_suffix(tokenStack)) { ScopeSequence seq{ - .scopes = {std::move(std::get<0>(*suffix))}}; + .scopes = {std::move(*identifier)}}; tokenStack.pop_back(); tokenStack.emplace_back(std::move(seq)); @@ -214,10 +228,10 @@ namespace mimicpp::printing::type::parsing return true; } - if (std::optional suffix = match_suffix(tokenStack)) + if (auto* identifier = match_suffix(tokenStack)) { Type newType{ - .identifier = {std::move(std::get<0>(*suffix))}}; + .identifier = {std::move(*identifier)}}; tokenStack.pop_back(); tokenStack.emplace_back(std::move(newType)); From c8e21585b85393df38821ad668b5b38869431b5c Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 14 Apr 2025 16:20:15 +0200 Subject: [PATCH 014/130] feat: type::parsing::NameParser handles specs --- include/mimic++/printing/type/NameParser.hpp | 218 +++++++++++++++++-- test/unit-tests/printing/TypeNameParser.cpp | 160 ++++++++++++++ 2 files changed, 358 insertions(+), 20 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 81cc8363d..2173bcc81 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -29,6 +29,13 @@ namespace mimicpp::printing::type::parsing visitor.add_identifier(content); visitor.add_scope(); + visitor.add_const(); + visitor.add_volatile(); + visitor.add_noexcept(); + visitor.add_ptr(); + visitor.add_lvalue_ref(); + visitor.add_rvalue_ref(); + visitor.begin_name(); visitor.end_name(); @@ -53,7 +60,7 @@ namespace mimicpp::printing::type::parsing StringViewT content{}; template - void operator()(Visitor& visitor) const + constexpr void operator()(Visitor& visitor) const { MIMICPP_ASSERT(!content.empty(), "Empty identifier is not allowed."); @@ -69,7 +76,7 @@ namespace mimicpp::printing::type::parsing std::vector scopes{}; template - void operator()(Visitor& visitor) const + constexpr void operator()(Visitor& visitor) const { MIMICPP_ASSERT(!scopes.empty(), "Empty scope-sequence is not allowed."); @@ -83,14 +90,102 @@ namespace mimicpp::printing::type::parsing } }; + class Specs + { + public: + struct Layer + { + bool isConst{false}; + bool isVolatile{false}; + bool isNoexcept{false}; + bool isLValueRef{false}; + bool isRValueRef{false}; + + constexpr void merge(Layer const& others) noexcept + { + MIMICPP_ASSERT(!(isConst && others.isConst), "Merging same specs."); + MIMICPP_ASSERT(!(isVolatile && others.isVolatile), "Merging same specs."); + MIMICPP_ASSERT(!(isNoexcept && others.isNoexcept), "Merging same specs."); + MIMICPP_ASSERT(!(isLValueRef && others.isLValueRef), "Merging same specs."); + MIMICPP_ASSERT(!(isRValueRef && others.isRValueRef), "Merging same specs."); + + MIMICPP_ASSERT(!(isLValueRef && others.isRValueRef), "Both reference types detected."); + MIMICPP_ASSERT(!(isRValueRef && others.isLValueRef), "Both reference types detected."); + + isConst = isConst || others.isConst; + isVolatile = isVolatile || others.isVolatile; + isNoexcept = isNoexcept || others.isNoexcept; + isLValueRef = isLValueRef || others.isLValueRef; + isRValueRef = isRValueRef || others.isRValueRef; + } + + template + constexpr void operator()(Visitor& visitor) const + { + auto& inner = unwrap_visitor(visitor); + + MIMICPP_ASSERT(!(isLValueRef && isRValueRef), "Both reference types detected."); + + if (isConst) + { + inner.add_const(); + } + + if (isVolatile) + { + inner.add_volatile(); + } + + if (isLValueRef) + { + inner.add_lvalue_ref(); + } + else if (isRValueRef) + { + inner.add_rvalue_ref(); + } + + if (isNoexcept) + { + inner.add_noexcept(); + } + } + }; + + std::vector layers{1u}; + + [[nodiscard]] + constexpr bool has_ptr() const noexcept + { + return 1u < layers.size(); + } + + template + constexpr void operator()(Visitor& visitor) const + { + MIMICPP_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); + } + } + }; + class Type { public: std::optional scopes{}; Identifier identifier; + Specs specs{}; template - void operator()(Visitor& visitor) const + constexpr void operator()(Visitor& visitor) const { auto& unwrapped = unwrap_visitor(visitor); @@ -105,6 +200,7 @@ namespace mimicpp::printing::type::parsing std::invoke(identifier, unwrapped); unwrapped.end_name(); + std::invoke(specs, unwrapped); unwrapped.end_type(); } }; @@ -113,6 +209,7 @@ namespace mimicpp::printing::type::parsing using Token = std::variant< token::Identifier, token::ScopeSequence, + token::Specs, token::Type>; using TokenStack = std::vector; @@ -127,7 +224,7 @@ namespace mimicpp::printing::type::parsing [[nodiscard]] constexpr bool is_suffix_of( [[maybe_unused]] util::type_list const types, - std::span const tokenStack) + std::span const tokenStack) noexcept { if (tokenStack.empty() || !std::holds_alternative(tokenStack.back())) @@ -186,6 +283,12 @@ namespace mimicpp::printing::type::parsing } } + constexpr void remove_suffix(std::span& tokenStack, std::size_t const count) noexcept + { + MIMICPP_ASSERT(count <= tokenStack.size(), "Count exceeds stack size."); + tokenStack = tokenStack.first(tokenStack.size() - count); + } + namespace token { inline bool try_reduce_as_scope_sequence(TokenStack& tokenStack) @@ -215,30 +318,81 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_type(TokenStack& tokenStack) { - if (std::optional suffix = match_suffix(tokenStack)) + std::span pendingTokens{tokenStack}; + + Specs* suffixSpecs{}; + if (auto* specs = match_suffix(pendingTokens)) { - auto& [scopeSeq, id] = *suffix; + suffixSpecs = specs; + remove_suffix(pendingTokens, 1u); + } - Type newType{ - .scopes = std::move(scopeSeq), - .identifier = std::move(id)}; - tokenStack.resize(tokenStack.size() - 2u); - tokenStack.emplace_back(std::move(newType)); + if (!is_suffix_of(pendingTokens)) + { + return false; + } - return true; + Type newType{ + .identifier = std::move(std::get(pendingTokens.back()))}; + remove_suffix(pendingTokens, 1u); + + if (suffixSpecs) + { + newType.specs = std::move(*suffixSpecs); } - if (auto* identifier = match_suffix(tokenStack)) + if (auto* seq = match_suffix(pendingTokens)) { - Type newType{ - .identifier = {std::move(*identifier)}}; - tokenStack.pop_back(); - tokenStack.emplace_back(std::move(newType)); + newType.scopes = std::move(*seq); + remove_suffix(pendingTokens, 1u); + } - return true; + if (auto* prefixSpecs = match_suffix(pendingTokens)) + { + auto& layers = prefixSpecs->layers; + MIMICPP_ASSERT(1u == layers.size(), "Prefix specs can not have more than one layer."); + auto& specs = layers.front(); + MIMICPP_ASSERT(!specs.isLValueRef && !specs.isRValueRef && !specs.isNoexcept, "Invalid prefix specs."); + newType.specs.layers.front().merge(specs); + remove_suffix(pendingTokens, 1u); } - return false; + tokenStack.resize(pendingTokens.size()); + tokenStack.emplace_back(std::move(newType)); + + return true; + } + + inline void add_specs(Specs::Layer newSpecs, TokenStack& tokenStack) + { + if (auto* specs = match_suffix(tokenStack)) + { + auto& layers = specs->layers; + MIMICPP_ASSERT(!layers.empty(), "Invalid specs state."); + layers.back().merge(newSpecs); + } + else + { + tokenStack.emplace_back( + Specs{.layers = {std::move(newSpecs)}}); + } + } + + inline void add_specs_layer(TokenStack& tokenStack) + { + if (auto* specs = match_suffix(tokenStack)) + { + auto& layers = specs->layers; + MIMICPP_ASSERT(!layers.empty(), "Invalid specs state."); + layers.emplace_back(); + } + else + { + tokenStack.emplace_back( + Specs{ + .layers = {2u, Specs::Layer{}} + }); + } } } @@ -324,8 +478,20 @@ namespace mimicpp::printing::type::parsing token::Identifier{.content = identifier.content}); } - constexpr void handle_lexer_token([[maybe_unused]] lexing::keyword const& keyword) + constexpr void handle_lexer_token(lexing::keyword const& keyword) { + if (constKeyword == keyword) + { + token::add_specs({.isConst = true}, m_TokenStack); + } + else if (volatileKeyword == keyword) + { + token::add_specs({.isVolatile = true}, m_TokenStack); + } + else if (noexceptKeyword == keyword) + { + token::add_specs({.isNoexcept = true}, m_TokenStack); + } } constexpr void handle_lexer_token(lexing::operator_or_punctuator const& token) @@ -334,6 +500,18 @@ namespace mimicpp::printing::type::parsing { token::try_reduce_as_scope_sequence(m_TokenStack); } + else if (lvalueRef == token) + { + token::add_specs({.isLValueRef = true}, m_TokenStack); + } + else if (rvalueRef == token) + { + token::add_specs({.isRValueRef = true}, m_TokenStack); + } + else if (pointer == token) + { + token::add_specs_layer(m_TokenStack); + } } }; } diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index c02ba4d6b..7bf04f314 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -24,6 +24,49 @@ namespace Mock begin_type{{.name = "VisitorMock::begin_type"}}; Mock end_type{{.name = "VisitorMock::end_type"}}; + + Mock add_const{{.name = "VisitorMock::add_const"}}; + Mock add_volatile{{.name = "VisitorMock::add_volatile"}}; + Mock add_noexcept{{.name = "VisitorMock::add_noexcept"}}; + Mock add_ptr{{.name = "VisitorMock::add_ptr"}}; + Mock add_lvalue_ref{{.name = "VisitorMock::add_lvalue_ref"}}; + Mock add_rvalue_ref{{.name = "VisitorMock::add_rvalue_ref"}}; + + [[nodiscard]] + auto expect_spec_call(StringViewT 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(); + } + + util::unreachable(); + } }; } @@ -100,3 +143,120 @@ TEST_CASE( parser(); } } + +TEST_CASE( + "parsing::NameParser detects specifications.", + "[print][print::type]") +{ + VisitorMock visitor{}; + ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("When given before the actual identifier.") + { + StringT const spec = GENERATE("const", "volatile"); + StringT const input{spec + " foo"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_name.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_name.expect_call(); + sequence += visitor.expect_spec_call(spec); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When given before the actual identifier with ref/pointer.") + { + StringT const spec = GENERATE("const", "volatile"); + StringT const indirection = GENERATE("&", "&&", "*"); + StringT const input{spec + " foo " + indirection}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_name.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_name.expect_call(); + sequence += visitor.expect_spec_call(spec); + sequence += visitor.expect_spec_call(indirection); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When given after the actual identifier.") + { + StringT const spec = GENERATE("const", "volatile"); + StringT const input{"foo " + spec}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_name.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_name.expect_call(); + sequence += visitor.expect_spec_call(spec); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When given after the actual identifier with ref/pointer.") + { + StringT const spec = GENERATE("const", "volatile"); + StringT const indirection = GENERATE("&", "&&", "*"); + StringT const input{"foo " + spec + indirection}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_name.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_name.expect_call(); + sequence += visitor.expect_spec_call(spec); + sequence += visitor.expect_spec_call(indirection); + sequence += visitor.end_type.expect_call(); + + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("Coming all together") + { + StringT const input{"volatile foo const* volatile** const&"}; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_name.expect_call(); + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.end_name.expect_call(); + + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } +} From 742e6380b9cd99f0d854ae59c6e38633bcf3bb96 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 14 Apr 2025 17:01:57 +0200 Subject: [PATCH 015/130] fix: correct impl of match_suffix with multiple suffix-elements --- include/mimic++/printing/type/NameParser.hpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 2173bcc81..ffdc9c0cd 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -273,10 +273,13 @@ namespace mimicpp::printing::type::parsing { auto const suffix = tokenStack.last(1u + sizeof...(Others)); - std::size_t i{0u}; - result = std::tie( - std::get(suffix[0u]), - std::get(suffix[++i])...); + 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; From 19838a23e2a79efbfbb26b593bb41579e874f1cc Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 14 Apr 2025 17:58:34 +0200 Subject: [PATCH 016/130] feat: type::parsing::NameParser handles templates --- include/mimic++/printing/type/NameParser.hpp | 176 ++++++++++++++- test/unit-tests/printing/TypeNameParser.cpp | 221 ++++++++++++++++--- 2 files changed, 364 insertions(+), 33 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index ffdc9c0cd..d915eb1de 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -27,7 +27,7 @@ namespace mimicpp::printing::type::parsing visitor.end(); visitor.add_identifier(content); - visitor.add_scope(); + visitor.add_arg(); visitor.add_const(); visitor.add_volatile(); @@ -36,11 +36,14 @@ namespace mimicpp::printing::type::parsing visitor.add_lvalue_ref(); visitor.add_rvalue_ref(); - visitor.begin_name(); - visitor.end_name(); + visitor.begin_scope(); + visitor.end_scope(); visitor.begin_type(); visitor.end_type(); + + visitor.begin_template_args(); + visitor.end_template_args(); }; template @@ -54,10 +57,50 @@ namespace mimicpp::printing::type::parsing namespace token { + class Type; + + class ArgSeparator + { + }; + + class OpeningAngle + { + }; + + class ClosingAngle + { + }; + + 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: StringViewT content{}; + std::optional templateArgs{}; + + [[nodiscard]] + constexpr bool is_template() const noexcept + { + return templateArgs.has_value(); + } template constexpr void operator()(Visitor& visitor) const @@ -67,6 +110,11 @@ namespace mimicpp::printing::type::parsing auto& unwrapped = unwrap_visitor(visitor); unwrapped.add_identifier(content); + + if (templateArgs) + { + templateArgs->handle_as_template_args(unwrapped); + } } }; @@ -84,8 +132,9 @@ namespace mimicpp::printing::type::parsing for (auto const& id : scopes) { + unwrapped.begin_scope(); std::invoke(id, unwrapped); - unwrapped.add_scope(); + unwrapped.end_scope(); } } }; @@ -190,7 +239,6 @@ namespace mimicpp::printing::type::parsing auto& unwrapped = unwrap_visitor(visitor); unwrapped.begin_type(); - unwrapped.begin_name(); if (scopes) { @@ -198,17 +246,54 @@ namespace mimicpp::printing::type::parsing } std::invoke(identifier, unwrapped); - - unwrapped.end_name(); std::invoke(specs, unwrapped); unwrapped.end_type(); } }; + + 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::invoke(*this, unwrapped); + unwrapped.end_template_args(); + } } using Token = std::variant< + token::ArgSeparator, + token::OpeningAngle, + token::ClosingAngle, + token::Identifier, token::ScopeSequence, + token::ArgSequence, token::Specs, token::Type>; using TokenStack = std::vector; @@ -294,6 +379,64 @@ namespace mimicpp::printing::type::parsing namespace token { + constexpr bool try_reduce_as_arg_sequence(TokenStack& tokenStack) + { + if (std::optional suffix = match_suffix(tokenStack)) + { + auto& [seq, sep, type] = *suffix; + + seq.types.emplace_back(std::move(type)); + tokenStack.resize(tokenStack.size() - 2u); + + return true; + } + + if (auto* type = match_suffix(tokenStack)) + { + ArgSequence seq{}; + seq.types.emplace_back(std::move(*type)); + tokenStack.pop_back(); + tokenStack.emplace_back(std::move(seq)); + + return true; + } + + return false; + } + + constexpr bool try_reduce_as_template_identifier(TokenStack& tokenStack) + { + if (std::optional suffix = match_suffix(tokenStack)) + { + auto& [id, opening, args, closing] = *suffix; + if (id.is_template()) + { + return false; + } + + id.templateArgs = std::move(args); + tokenStack.resize(tokenStack.size() - 3u); + + return true; + } + + if (std::optional suffix = match_suffix(tokenStack)) + { + auto& [id, opening, closing] = *suffix; + if (id.is_template()) + { + return false; + } + + id.templateArgs.emplace(); + tokenStack.resize(tokenStack.size() - 2u); + + return true; + } + + return false; + } + inline bool try_reduce_as_scope_sequence(TokenStack& tokenStack) { if (std::optional suffix = match_suffix(tokenStack)) @@ -503,6 +646,13 @@ namespace mimicpp::printing::type::parsing { token::try_reduce_as_scope_sequence(m_TokenStack); } + else if (commaSeparator == token) + { + token::try_reduce_as_type(m_TokenStack) + && token::try_reduce_as_arg_sequence(m_TokenStack); + + m_TokenStack.emplace_back(token::ArgSeparator{}); + } else if (lvalueRef == token) { token::add_specs({.isLValueRef = true}, m_TokenStack); @@ -515,6 +665,18 @@ namespace mimicpp::printing::type::parsing { token::add_specs_layer(m_TokenStack); } + else if (openingAngle == token) + { + m_TokenStack.emplace_back(token::OpeningAngle{}); + } + else if (closingAngle == token) + { + token::try_reduce_as_type(m_TokenStack) + && token::try_reduce_as_arg_sequence(m_TokenStack); + + m_TokenStack.emplace_back(token::ClosingAngle{}); + token::try_reduce_as_template_identifier(m_TokenStack); + } } }; } diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 7bf04f314..1b53381f5 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -17,14 +17,17 @@ namespace Mock end{{.name = "VisitorMock::end"}}; Mock add_identifier{{.name = "VisitorMock::add_identifier"}}; - Mock add_scope{{.name = "VisitorMock::add_scope"}}; + Mock add_arg{{.name = "VisitorMock::add_arg"}}; - Mock begin_name{{.name = "VisitorMock::begin_name"}}; - Mock end_name{{.name = "VisitorMock::end_name"}}; + Mock begin_scope{{.name = "VisitorMock::begin_scope"}}; + Mock end_scope{{.name = "VisitorMock::end_scope"}}; Mock begin_type{{.name = "VisitorMock::begin_type"}}; Mock end_type{{.name = "VisitorMock::end_type"}}; + Mock begin_template_args{{.name = "VisitorMock::begin_template_args"}}; + Mock end_template_args{{.name = "VisitorMock::end_template_args"}}; + Mock add_const{{.name = "VisitorMock::add_const"}}; Mock add_volatile{{.name = "VisitorMock::add_volatile"}}; Mock add_noexcept{{.name = "VisitorMock::add_noexcept"}}; @@ -87,9 +90,7 @@ TEST_CASE( CAPTURE(input); sequence += visitor.begin_type.expect_call(); - sequence += visitor.begin_name.expect_call(); sequence += visitor.add_identifier.expect_call(input); - sequence += visitor.end_name.expect_call(); sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); @@ -108,17 +109,18 @@ TEST_CASE( CAPTURE(input); sequence += visitor.begin_type.expect_call(); - sequence += visitor.begin_name.expect_call(); + sequence += visitor.begin_scope.expect_call(); sequence += visitor.add_identifier.expect_call(firstScope); - sequence += visitor.add_scope.expect_call(); + sequence += visitor.end_scope.expect_call(); + + sequence += visitor.begin_scope.expect_call(); sequence += visitor.add_identifier.expect_call(secondScope); - sequence += visitor.add_scope.expect_call(); + sequence += visitor.end_scope.expect_call(); + sequence += visitor.add_identifier.expect_call(thirdScope); - sequence += visitor.end_name.expect_call(); sequence += visitor.end_type.expect_call(); - sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -132,9 +134,7 @@ TEST_CASE( CAPTURE(input); sequence += visitor.begin_type.expect_call(); - sequence += visitor.begin_name.expect_call(); sequence += visitor.add_identifier.expect_call(scope); - sequence += visitor.end_name.expect_call(); sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); @@ -160,12 +160,11 @@ TEST_CASE( CAPTURE(input); sequence += visitor.begin_type.expect_call(); - sequence += visitor.begin_name.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.end_name.expect_call(); + sequence += visitor.expect_spec_call(spec); - sequence += visitor.end_type.expect_call(); + sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -180,13 +179,12 @@ TEST_CASE( CAPTURE(input); sequence += visitor.begin_type.expect_call(); - sequence += visitor.begin_name.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.end_name.expect_call(); + sequence += visitor.expect_spec_call(spec); sequence += visitor.expect_spec_call(indirection); - sequence += visitor.end_type.expect_call(); + sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -200,12 +198,11 @@ TEST_CASE( CAPTURE(input); sequence += visitor.begin_type.expect_call(); - sequence += visitor.begin_name.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.end_name.expect_call(); + sequence += visitor.expect_spec_call(spec); - sequence += visitor.end_type.expect_call(); + sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -220,13 +217,12 @@ TEST_CASE( CAPTURE(input); sequence += visitor.begin_type.expect_call(); - sequence += visitor.begin_name.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.end_name.expect_call(); + sequence += visitor.expect_spec_call(spec); sequence += visitor.expect_spec_call(indirection); - sequence += visitor.end_type.expect_call(); + sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -239,9 +235,7 @@ TEST_CASE( CAPTURE(input); sequence += visitor.begin_type.expect_call(); - sequence += visitor.begin_name.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.end_name.expect_call(); sequence += visitor.add_const.expect_call(); sequence += visitor.add_volatile.expect_call(); @@ -253,7 +247,182 @@ TEST_CASE( sequence += visitor.add_lvalue_ref.expect_call(); sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } +} + +TEST_CASE( + "parsing::NameParser detects templates.", + "[print][print::type]") +{ + VisitorMock visitor{}; + ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("When templated identifier with 0 args is given.") + { + StringViewT 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(); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When qualified templated identifier is given.") + { + StringViewT 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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When templated identifier with multiple args is given.") + { + StringViewT 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(); + + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When templated identifier with multiple args with specs is given.") + { + StringViewT 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(); + + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When templated identifier has templated arg.") + { + StringViewT 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(); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("bar"); + + { + sequence += visitor.begin_template_args.expect_call(); + + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + SECTION("When template is part of a scope.") + { + StringViewT 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(); + 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(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; From bd06a84fac6cebdcbf55a7c187540138cb1fb519 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 14 Apr 2025 18:02:43 +0200 Subject: [PATCH 017/130] chore: mark several functions as constexpr --- include/mimic++/printing/type/NameParser.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index d915eb1de..afcddb0c4 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -437,7 +437,7 @@ namespace mimicpp::printing::type::parsing return false; } - inline bool try_reduce_as_scope_sequence(TokenStack& tokenStack) + constexpr bool try_reduce_as_scope_sequence(TokenStack& tokenStack) { if (std::optional suffix = match_suffix(tokenStack)) { @@ -462,7 +462,7 @@ namespace mimicpp::printing::type::parsing return false; } - inline bool try_reduce_as_type(TokenStack& tokenStack) + constexpr bool try_reduce_as_type(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; @@ -509,7 +509,7 @@ namespace mimicpp::printing::type::parsing return true; } - inline void add_specs(Specs::Layer newSpecs, TokenStack& tokenStack) + constexpr void add_specs(Specs::Layer newSpecs, TokenStack& tokenStack) { if (auto* specs = match_suffix(tokenStack)) { @@ -524,7 +524,7 @@ namespace mimicpp::printing::type::parsing } } - inline void add_specs_layer(TokenStack& tokenStack) + constexpr void add_specs_layer(TokenStack& tokenStack) { if (auto* specs = match_suffix(tokenStack)) { @@ -547,13 +547,13 @@ namespace mimicpp::printing::type::parsing { public: [[nodiscard]] - explicit NameParser(Visitor visitor, StringViewT content) noexcept(std::is_nothrow_move_constructible_v) + explicit constexpr NameParser(Visitor visitor, StringViewT content) noexcept(std::is_nothrow_move_constructible_v) : m_Visitor{std::move(visitor)}, m_Lexer{std::move(content)} { } - void operator()() + constexpr void operator()() { visitor().begin(); From 5ab16ee663176497246bffb9810f08329bdc7722 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 14 Apr 2025 21:21:43 +0200 Subject: [PATCH 018/130] feat: type::parsing::NameParser handles functions --- include/mimic++/printing/type/NameParser.hpp | 453 ++++++++++++++----- test/unit-tests/printing/TypeNameParser.cpp | 414 +++++++++++++++++ 2 files changed, 766 insertions(+), 101 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index afcddb0c4..43df3f7af 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -26,9 +26,18 @@ namespace mimicpp::printing::type::parsing 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(); + visitor.end_template_args(); + visitor.add_const(); visitor.add_volatile(); visitor.add_noexcept(); @@ -36,14 +45,12 @@ namespace mimicpp::printing::type::parsing visitor.add_lvalue_ref(); visitor.add_rvalue_ref(); - visitor.begin_scope(); - visitor.end_scope(); - - visitor.begin_type(); - visitor.end_type(); - - visitor.begin_template_args(); - visitor.end_template_args(); + visitor.begin_function(); + visitor.end_function(); + visitor.begin_return_type(); + visitor.end_return_type(); + visitor.begin_function_args(); + visitor.end_function_args(); }; template @@ -71,72 +78,12 @@ namespace mimicpp::printing::type::parsing { }; - class ArgSequence + class OpeningParens { - 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 + class ClosingParens { - public: - StringViewT content{}; - std::optional templateArgs{}; - - [[nodiscard]] - constexpr bool is_template() const noexcept - { - return templateArgs.has_value(); - } - - template - constexpr void operator()(Visitor& visitor) const - { - MIMICPP_ASSERT(!content.empty(), "Empty identifier is not allowed."); - - auto& unwrapped = unwrap_visitor(visitor); - - unwrapped.add_identifier(content); - - if (templateArgs) - { - templateArgs->handle_as_template_args(unwrapped); - } - } - }; - - class ScopeSequence - { - public: - std::vector scopes{}; - - template - constexpr void operator()(Visitor& visitor) const - { - MIMICPP_ASSERT(!scopes.empty(), "Empty scope-sequence is not allowed."); - - auto& unwrapped = unwrap_visitor(visitor); - - for (auto const& id : scopes) - { - unwrapped.begin_scope(); - std::invoke(id, unwrapped); - unwrapped.end_scope(); - } - } }; class Specs @@ -226,7 +173,118 @@ namespace mimicpp::printing::type::parsing } }; - class Type + 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; + + template + constexpr void handle_as_function_args(Visitor& visitor) const; + }; + + class Identifier + { + public: + StringViewT content{}; + std::optional templateArgs{}; + + [[nodiscard]] + constexpr bool is_template() const noexcept + { + return templateArgs.has_value(); + } + + template + constexpr void operator()(Visitor& visitor) const + { + MIMICPP_ASSERT(!content.empty(), "Empty identifier is not allowed."); + + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.add_identifier(content); + + if (templateArgs) + { + templateArgs->handle_as_template_args(unwrapped); + } + } + }; + + class FunctionIdentifier + { + public: + std::optional identifier{}; + ArgSequence args{}; + Specs specs{}; + + template + void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + if (identifier) + { + std::invoke(*identifier, unwrapped); + } + + args.handle_as_function_args(unwrapped); + std::invoke(specs, unwrapped); + } + }; + + class ScopeSequence + { + public: + using Id = std::variant; + std::vector scopes{}; + + template + constexpr void operator()(Visitor& visitor) const + { + MIMICPP_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_identifier(id, unwrapped); }, + scope); + unwrapped.end_scope(); + } + } + + private: + template + constexpr void handle_identifier(Identifier const& id, Visitor& visitor) const + { + std::invoke(id, visitor); + } + + template + constexpr void handle_identifier(FunctionIdentifier const& id, Visitor& visitor) const + { + visitor.begin_function(); + std::invoke(id, visitor); + visitor.end_function(); + } + }; + + class RegularType { public: std::optional scopes{}; @@ -238,8 +296,6 @@ namespace mimicpp::printing::type::parsing { auto& unwrapped = unwrap_visitor(visitor); - unwrapped.begin_type(); - if (scopes) { std::invoke(*scopes, unwrapped); @@ -247,6 +303,70 @@ namespace mimicpp::printing::type::parsing std::invoke(identifier, unwrapped); std::invoke(specs, unwrapped); + } + }; + + class FunctionType + { + 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(); + } + + private: + template + static constexpr void handle_description([[maybe_unused]] Visitor& visitor, [[maybe_unused]] std::monostate const state) + { + } + + template + static constexpr void handle_description(Visitor& visitor, Description const& description) + { + std::invoke(description, visitor); + } + }; + + class Type + { + public: + using State = std::variant; + State m_State; + + template + void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_type(); + + std::visit( + [&](auto const& inner) { std::invoke(inner, unwrapped); }, + m_State); + unwrapped.end_type(); } }; @@ -284,14 +404,27 @@ namespace mimicpp::printing::type::parsing std::invoke(*this, unwrapped); unwrapped.end_template_args(); } + + template + constexpr void ArgSequence::handle_as_function_args(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_function_args(); + std::invoke(*this, unwrapped); + unwrapped.end_function_args(); + } } using Token = std::variant< token::ArgSeparator, token::OpeningAngle, token::ClosingAngle, + token::OpeningParens, + token::ClosingParens, token::Identifier, + token::FunctionIdentifier, token::ScopeSequence, token::ArgSequence, token::Specs, @@ -379,6 +512,40 @@ namespace mimicpp::printing::type::parsing namespace token { + bool try_reduce_as_type(TokenStack& tokenStack); + + constexpr bool try_reduce_as_scope_sequence(TokenStack& tokenStack) + { + ScopeSequence::Id id{}; + if (auto const* identifier = match_suffix(tokenStack)) + { + id = std::move(*identifier); + } + else if (auto const* funIdentifier = match_suffix(tokenStack)) + { + id = std::move(*funIdentifier); + } + else + { + return false; + } + + tokenStack.pop_back(); + + if (auto* sequence = match_suffix(tokenStack)) + { + sequence->scopes.emplace_back(std::move(id)); + } + else + { + tokenStack.emplace_back( + ScopeSequence{ + .scopes = {std::move(id)}}); + } + + return true; + } + constexpr bool try_reduce_as_arg_sequence(TokenStack& tokenStack) { if (std::optional suffix = match_suffix(tokenStack)) @@ -437,32 +604,7 @@ namespace mimicpp::printing::type::parsing return false; } - constexpr bool try_reduce_as_scope_sequence(TokenStack& tokenStack) - { - if (std::optional suffix = match_suffix(tokenStack)) - { - auto& [scopeSeq, id] = *suffix; - - scopeSeq.scopes.emplace_back(std::move(id)); - tokenStack.pop_back(); - - return true; - } - - if (auto* identifier = match_suffix(tokenStack)) - { - ScopeSequence seq{ - .scopes = {std::move(*identifier)}}; - tokenStack.pop_back(); - tokenStack.emplace_back(std::move(seq)); - - return true; - } - - return false; - } - - constexpr bool try_reduce_as_type(TokenStack& tokenStack) + constexpr bool try_reduce_as_regular_type(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; @@ -478,7 +620,7 @@ namespace mimicpp::printing::type::parsing return false; } - Type newType{ + RegularType newType{ .identifier = std::move(std::get(pendingTokens.back()))}; remove_suffix(pendingTokens, 1u); @@ -504,11 +646,108 @@ namespace mimicpp::printing::type::parsing } tokenStack.resize(pendingTokens.size()); - tokenStack.emplace_back(std::move(newType)); + tokenStack.emplace_back( + std::in_place_type, + std::move(newType)); + + return true; + } + + inline bool try_reduce_as_function_type(TokenStack& tokenStack) + { + if (auto* funIdentifier = match_suffix(tokenStack)) + { + FunctionType funType{ + .identifier = std::move(*funIdentifier)}; + tokenStack.pop_back(); + + if (auto* scopes = match_suffix(tokenStack)) + { + funType.scopes = std::move(*scopes); + tokenStack.pop_back(); + } + + if (auto* returnType = match_suffix(tokenStack)) + { + funType.returnType = std::make_shared(std::move(*returnType)); + tokenStack.pop_back(); + } + + tokenStack.emplace_back( + std::in_place_type, + std::move(funType)); + + return true; + } + + return false; + } + + constexpr bool try_reduce_as_function_identifier(TokenStack& tokenStack) + { + std::span pendingStack{tokenStack}; + + Specs* funSpecs{}; + if (auto* specs = match_suffix(pendingStack)) + { + funSpecs = specs; + remove_suffix(pendingStack, 1u); + } + + if (!is_suffix_of(pendingStack)) + { + return false; + } + remove_suffix(pendingStack, 1u); + + ArgSequence* funArgs{}; + if (auto* args = match_suffix(pendingStack)) + { + funArgs = args; + remove_suffix(pendingStack, 1u); + } + + if (!is_suffix_of(pendingStack)) + { + return false; + } + remove_suffix(pendingStack, 1u); + + FunctionIdentifier funIdentifier{}; + if (auto* identifier = match_suffix(pendingStack)) + { + funIdentifier.identifier = std::move(*identifier); + remove_suffix(pendingStack, 1u); + } + + if (funSpecs) + { + funIdentifier.specs = std::move(*funSpecs); + } + + if (funArgs) + { + funIdentifier.args = std::move(*funArgs); + } + + tokenStack.resize(pendingStack.size()); + + // There may be something similar to a return type in front, which hasn't been reduced yet. + try_reduce_as_type(tokenStack); + + tokenStack.emplace_back(std::move(funIdentifier)); return true; } + inline bool try_reduce_as_type(TokenStack& tokenStack) + { + bool const isFunction = token::try_reduce_as_function_identifier(tokenStack) + && token::try_reduce_as_function_type(tokenStack); + return isFunction + || try_reduce_as_regular_type(tokenStack); + } + constexpr void add_specs(Specs::Layer newSpecs, TokenStack& tokenStack) { if (auto* specs = match_suffix(tokenStack)) @@ -644,6 +883,7 @@ namespace mimicpp::printing::type::parsing { if (scopeResolution == token) { + token::try_reduce_as_function_identifier(m_TokenStack); token::try_reduce_as_scope_sequence(m_TokenStack); } else if (commaSeparator == token) @@ -677,6 +917,17 @@ namespace mimicpp::printing::type::parsing m_TokenStack.emplace_back(token::ClosingAngle{}); token::try_reduce_as_template_identifier(m_TokenStack); } + else if (openingParens == token) + { + m_TokenStack.emplace_back(token::OpeningParens{}); + } + else if (closingParens == token) + { + token::try_reduce_as_type(m_TokenStack) + && token::try_reduce_as_arg_sequence(m_TokenStack); + + m_TokenStack.emplace_back(token::ClosingParens{}); + } } }; } diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 1b53381f5..95e707cfa 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -28,6 +28,13 @@ namespace Mock begin_template_args{{.name = "VisitorMock::begin_template_args"}}; Mock end_template_args{{.name = "VisitorMock::end_template_args"}}; + Mock begin_function{{.name = "VisitorMock::begin_function"}}; + Mock end_function{{.name = "VisitorMock::end_function"}}; + Mock begin_return_type{{.name = "VisitorMock::begin_return_type"}}; + Mock end_return_type{{.name = "VisitorMock::end_return_type"}}; + Mock begin_function_args{{.name = "VisitorMock::begin_function_args"}}; + Mock end_function_args{{.name = "VisitorMock::end_function_args"}}; + Mock add_const{{.name = "VisitorMock::add_const"}}; Mock add_volatile{{.name = "VisitorMock::add_volatile"}}; Mock add_noexcept{{.name = "VisitorMock::add_noexcept"}}; @@ -429,3 +436,410 @@ TEST_CASE( parser(); } } + +TEST_CASE( + "parsing::NameParser detects named functions.", + "[print][print::type]") +{ + StringT const spec = GENERATE("", "const", "volatile", "noexcept", "&", "&&"); + StringT const specSpace = GENERATE("", " "); + StringT const suffix = specSpace + spec; + + VisitorMock visitor{}; + ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_function.expect_call(); + + SECTION("When function identifier with 0 args is given.") + { + StringT const returnType = GENERATE("", "void"); + StringT const prefix = returnType + (returnType.empty() ? "" : " "); + StringT 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(); + 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_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When templated function identifier is given.") + { + StringT const returnType = GENERATE("", "void"); + StringT const prefix = returnType + (returnType.empty() ? "" : " "); + StringT 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(); + 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(); + 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_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When function identifier with single arg is given.") + { + StringT const returnType = GENERATE("", "void"); + StringT const prefix = returnType + (returnType.empty() ? "" : " "); + StringT 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(); + + 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_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When function identifier with templated arg is given.") + { + StringT const returnType = GENERATE("", "void"); + StringT const prefix = returnType + (returnType.empty() ? "" : " "); + StringT 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(); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("bar"); + + sequence += visitor.begin_template_args.expect_call(); + 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_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When function identifier with multiple args is given.") + { + StringT const returnType = GENERATE("", "void"); + StringT const prefix = returnType + (returnType.empty() ? "" : " "); + StringT 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(); + + 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_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When function is a scope.") + { + StringT const returnType = GENERATE("", "void"); + StringT const prefix = returnType + (returnType.empty() ? "" : " "); + StringT const templateExpr = GENERATE("", "<>"); + StringT const scopeSpec = GENERATE("", "const", "volatile", "&", "&&", "noexcept"); + StringT 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(); + sequence += visitor.end_template_args.expect_call(); + } + + sequence += visitor.begin_function_args.expect_call(); + 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(); + 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_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When function identifier with qualified return type is given.") + { + StringT 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(); + 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_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When function identifier with templated return type is given.") + { + StringT 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(); + 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(); + 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_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } +} + +TEST_CASE( + "parsing::NameParser detects function types.", + "[print][print::type]") +{ + VisitorMock visitor{}; + ScopedSequence sequence{}; + + StringT const spec = GENERATE("", "const", "volatile", "noexcept", "&", "&&"); + StringT const specSpace = GENERATE("", " "); + StringT const suffix = specSpace + spec; + StringViewT const input = "foo ()" + suffix; + CAPTURE(input); + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_type.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("foo"); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.begin_function_args.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_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); +} From 24d0615b7fdf30ac5a70de64cff149e80be22c05 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 15 Apr 2025 11:11:33 +0200 Subject: [PATCH 019/130] fix: parsing::NameParser correctly detects function-types --- include/mimic++/printing/type/NameParser.hpp | 35 ++++++++-- test/unit-tests/printing/TypeNameParser.cpp | 68 +++++++++++++++----- 2 files changed, 80 insertions(+), 23 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 43df3f7af..8f61de846 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -66,6 +66,10 @@ namespace mimicpp::printing::type::parsing { class Type; + class Space + { + }; + class ArgSeparator { }; @@ -417,6 +421,7 @@ namespace mimicpp::printing::type::parsing } using Token = std::variant< + token::Space, token::ArgSeparator, token::OpeningAngle, token::ClosingAngle, @@ -514,6 +519,14 @@ namespace mimicpp::printing::type::parsing { bool try_reduce_as_type(TokenStack& tokenStack); + constexpr void ignore_space(std::span& tokenStack) noexcept + { + if (is_suffix_of(tokenStack)) + { + remove_suffix(tokenStack, 1u); + } + } + constexpr bool try_reduce_as_scope_sequence(TokenStack& tokenStack) { ScopeSequence::Id id{}; @@ -615,6 +628,7 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } + ignore_space(pendingTokens); if (!is_suffix_of(pendingTokens)) { return false; @@ -655,24 +669,26 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_function_type(TokenStack& tokenStack) { - if (auto* funIdentifier = match_suffix(tokenStack)) + std::span pendingTokens{tokenStack}; + if (auto* funIdentifier = match_suffix(pendingTokens)) { FunctionType funType{ .identifier = std::move(*funIdentifier)}; - tokenStack.pop_back(); + remove_suffix(pendingTokens, 1u); - if (auto* scopes = match_suffix(tokenStack)) + if (auto* scopes = match_suffix(pendingTokens)) { funType.scopes = std::move(*scopes); - tokenStack.pop_back(); + remove_suffix(pendingTokens, 1u); } - if (auto* returnType = match_suffix(tokenStack)) + if (auto* returnType = match_suffix(pendingTokens)) { funType.returnType = std::make_shared(std::move(*returnType)); - tokenStack.pop_back(); + remove_suffix(pendingTokens, 1u); } + tokenStack.resize(pendingTokens.size()); tokenStack.emplace_back( std::in_place_type, std::move(funType)); @@ -855,6 +871,13 @@ namespace mimicpp::printing::type::parsing constexpr void handle_lexer_token([[maybe_unused]] lexing::space const& space) { + // In some cases, a space after an identifier carries semantic meaning. + // I.e. consider these two type-names: `void ()` and `foo()`, + // where the former is a type returning void and the latter is a function named `foo`. + if (is_suffix_of(m_TokenStack)) + { + m_TokenStack.emplace_back(token::Space{}); + } } constexpr void handle_lexer_token(lexing::identifier const& identifier) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 95e707cfa..0213e717f 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -815,31 +815,65 @@ TEST_CASE( StringT const spec = GENERATE("", "const", "volatile", "noexcept", "&", "&&"); StringT const specSpace = GENERATE("", " "); StringT const suffix = specSpace + spec; - StringViewT const input = "foo ()" + suffix; - CAPTURE(input); sequence += visitor.begin.expect_call(); sequence += visitor.begin_type.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("foo"); - sequence += visitor.end_type.expect_call(); - sequence += visitor.end_return_type.expect_call(); + SECTION("When function has an unqualified return-type.") + { + StringT const input = "foo ()" + suffix; + CAPTURE(input); - sequence += visitor.begin_function_args.expect_call(); - sequence += visitor.end_function_args.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(); - CHECKED_IF(!spec.empty()) - { - sequence += visitor.expect_spec_call(spec); + sequence += visitor.begin_function_args.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_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); } - sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); - sequence += visitor.end.expect_call(); + SECTION("When function has a qualified return-type.") + { + StringT const input = "volatile foo const& ()" + suffix; + CAPTURE(input); + + 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(); + sequence += visitor.end_function_args.expect_call(); - printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + CHECKED_IF(!spec.empty()) + { + sequence += visitor.expect_spec_call(spec); + } + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } } From 3d73aa34f109f9630a9ca89074da6a097df31d03 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 15 Apr 2025 14:47:38 +0200 Subject: [PATCH 020/130] feat: parsing::NameParser detects placeholders --- include/mimic++/printing/type/NameParser.hpp | 400 +++++++++++++++---- test/unit-tests/printing/TypeNameParser.cpp | 194 +++++++++ 2 files changed, 508 insertions(+), 86 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 8f61de846..7c3631854 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -72,22 +72,56 @@ namespace mimicpp::printing::type::parsing class ArgSeparator { + public: + StringViewT content; }; class OpeningAngle { + public: + StringViewT content; }; class ClosingAngle { + public: + StringViewT content; }; class OpeningParens { + public: + StringViewT content; }; class ClosingParens { + public: + StringViewT content; + }; + + class OpeningCurly + { + public: + StringViewT content; + }; + + class ClosingCurly + { + public: + StringViewT content; + }; + + class OpeningBacktick + { + public: + StringViewT content; + }; + + class ClosingSingleQuote + { + public: + StringViewT content; }; class Specs @@ -205,12 +239,26 @@ namespace mimicpp::printing::type::parsing StringViewT content{}; std::optional templateArgs{}; + struct FunctionInfo + { + ArgSequence args; + Specs specs{}; + }; + + std::optional functionInfo{}; + [[nodiscard]] constexpr bool is_template() const noexcept { return templateArgs.has_value(); } + [[nodiscard]] + constexpr bool is_function() const noexcept + { + return functionInfo.has_value(); + } + template constexpr void operator()(Visitor& visitor) const { @@ -224,36 +272,19 @@ namespace mimicpp::printing::type::parsing { templateArgs->handle_as_template_args(unwrapped); } - } - }; - - class FunctionIdentifier - { - public: - std::optional identifier{}; - ArgSequence args{}; - Specs specs{}; - - template - void operator()(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - if (identifier) + if (functionInfo) { - std::invoke(*identifier, unwrapped); + functionInfo->args.handle_as_function_args(unwrapped); + std::invoke(functionInfo->specs, unwrapped); } - - args.handle_as_function_args(unwrapped); - std::invoke(specs, unwrapped); } }; class ScopeSequence { public: - using Id = std::variant; - std::vector scopes{}; + std::vector scopes{}; template constexpr void operator()(Visitor& visitor) const @@ -265,26 +296,20 @@ namespace mimicpp::printing::type::parsing for (auto const& scope : scopes) { unwrapped.begin_scope(); - std::visit( - [&](auto const& id) { handle_identifier(id, unwrapped); }, - scope); - unwrapped.end_scope(); - } - } + const bool isFunction = scope.is_function(); + if (isFunction) + { + unwrapped.begin_function(); + } - private: - template - constexpr void handle_identifier(Identifier const& id, Visitor& visitor) const - { - std::invoke(id, visitor); - } + std::invoke(scope, unwrapped); - template - constexpr void handle_identifier(FunctionIdentifier const& id, Visitor& visitor) const - { - visitor.begin_function(); - std::invoke(id, visitor); - visitor.end_function(); + if (isFunction) + { + unwrapped.end_function(); + } + unwrapped.end_scope(); + } } }; @@ -315,11 +340,13 @@ namespace mimicpp::printing::type::parsing public: std::shared_ptr returnType{}; std::optional scopes{}; - FunctionIdentifier identifier{}; + Identifier identifier{}; template void operator()(Visitor& visitor) const { + MIMICPP_ASSERT(identifier.is_function(), "Identifier must denote a function."); + auto& unwrapped = unwrap_visitor(visitor); unwrapped.begin_function(); @@ -336,7 +363,23 @@ namespace mimicpp::printing::type::parsing std::invoke(*scopes, unwrapped); } - std::invoke(identifier, unwrapped); + // Handle identifier manually, as it requires that the top-level name is not empty, which may be + // violated when an actual function-type is provided. + + if (auto const& name = identifier.content; + !name.empty()) + { + unwrapped.add_identifier(name); + } + + if (auto const& templateArgs = identifier.templateArgs) + { + templateArgs->handle_as_template_args(unwrapped); + } + + auto const& functionInfo = *identifier.functionInfo; + functionInfo.args.handle_as_function_args(unwrapped); + std::invoke(functionInfo.specs, unwrapped); unwrapped.end_function(); } @@ -427,9 +470,12 @@ namespace mimicpp::printing::type::parsing token::ClosingAngle, token::OpeningParens, token::ClosingParens, + token::OpeningCurly, + token::ClosingCurly, + token::OpeningBacktick, + token::ClosingSingleQuote, token::Identifier, - token::FunctionIdentifier, token::ScopeSequence, token::ArgSequence, token::Specs, @@ -529,31 +575,23 @@ namespace mimicpp::printing::type::parsing constexpr bool try_reduce_as_scope_sequence(TokenStack& tokenStack) { - ScopeSequence::Id id{}; - if (auto const* identifier = match_suffix(tokenStack)) - { - id = std::move(*identifier); - } - else if (auto const* funIdentifier = match_suffix(tokenStack)) - { - id = std::move(*funIdentifier); - } - else + if (!is_suffix_of(tokenStack)) { return false; } + auto identifier = std::get(std::move(tokenStack.back())); tokenStack.pop_back(); if (auto* sequence = match_suffix(tokenStack)) { - sequence->scopes.emplace_back(std::move(id)); + sequence->scopes.emplace_back(std::move(identifier)); } else { tokenStack.emplace_back( ScopeSequence{ - .scopes = {std::move(id)}}); + .scopes = {std::move(identifier)}}); } return true; @@ -617,6 +655,70 @@ namespace mimicpp::printing::type::parsing 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); + } + + template + constexpr bool try_reduce_as_placeholder_identifier_wrapped(TokenStack& tokenStack) + { + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Token-stack does not have the closing token as top."); + 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(); + StringViewT const content{opening.content.data(), contentLength}; + + pendingTokens = std::span{pendingTokens.begin(), openingIter.base() - 1}; + tokenStack.resize(pendingTokens.size()); + tokenStack.emplace_back(Identifier{.content = content}); + + return true; + } + + constexpr bool try_reduce_as_placeholder_identifier(TokenStack& tokenStack) + { + if (is_suffix_of(tokenStack)) + { + return try_reduce_as_placeholder_identifier_wrapped(tokenStack); + } + + if (is_suffix_of(tokenStack)) + { + return try_reduce_as_placeholder_identifier_wrapped(tokenStack); + } + + if (is_suffix_of(tokenStack)) + { + return try_reduce_as_placeholder_identifier_wrapped(tokenStack); + } + + if (is_suffix_of(tokenStack)) + { + return try_reduce_as_placeholder_identifier_wrapped(tokenStack); + } + + return false; + } + constexpr bool try_reduce_as_regular_type(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; @@ -628,7 +730,7 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } - ignore_space(pendingTokens); + // ignore_space(pendingTokens); if (!is_suffix_of(pendingTokens)) { return false; @@ -649,6 +751,8 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } + // When ScopeSequence or Identifier starts with a placeholder-like, there will be an additional space-token. + ignore_space(pendingTokens); if (auto* prefixSpecs = match_suffix(pendingTokens)) { auto& layers = prefixSpecs->layers; @@ -667,10 +771,12 @@ namespace mimicpp::printing::type::parsing return true; } - inline bool try_reduce_as_function_type(TokenStack& tokenStack) + inline bool try_reduce_as_named_function(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; - if (auto* funIdentifier = match_suffix(pendingTokens)) + if (auto* funIdentifier = match_suffix(pendingTokens); + funIdentifier + && funIdentifier->is_function()) { FunctionType funType{ .identifier = std::move(*funIdentifier)}; @@ -682,13 +788,26 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } + // When ScopeSequence or Identifier starts with a placeholder-like, there will be an additional space-token. + ignore_space(pendingTokens); if (auto* returnType = match_suffix(pendingTokens)) { funType.returnType = std::make_shared(std::move(*returnType)); remove_suffix(pendingTokens, 1u); } - tokenStack.resize(pendingTokens.size()); + tokenStack.resize( + std::exchange(pendingTokens, {}).size()); + + // There may be something similar to a return type in front, which hasn't been reduced yet. + if (!funType.returnType + && try_reduce_as_type(tokenStack)) + { + funType.returnType = std::make_shared( + std::get(std::move(tokenStack.back()))); + tokenStack.pop_back(); + } + tokenStack.emplace_back( std::in_place_type, std::move(funType)); @@ -699,6 +818,69 @@ namespace mimicpp::printing::type::parsing return false; } + inline bool try_reduce_as_unnamed_function(TokenStack& tokenStack) + { + std::span pendingStack{tokenStack}; + + Specs* funSpecs{}; + if (auto* specs = match_suffix(pendingStack)) + { + funSpecs = specs; + remove_suffix(pendingStack, 1u); + } + + if (!is_suffix_of(pendingStack)) + { + return false; + } + remove_suffix(pendingStack, 1u); + + ArgSequence* funArgs{}; + if (auto* args = match_suffix(pendingStack)) + { + funArgs = args; + remove_suffix(pendingStack, 1u); + } + + // The space is required, because the return type will always be spaced away from the parens. + if (!is_suffix_of(pendingStack)) + { + return false; + } + // Remove just the space and opening-parens, as we need to consume the type next. + remove_suffix(pendingStack, 2u); + + FunctionType funType{ + .returnType = std::make_shared( + std::get( + std::move(pendingStack.back())))}; + remove_suffix(pendingStack, 1u); + + auto& funIdentifier = funType.identifier.functionInfo.emplace(); + if (funSpecs) + { + funIdentifier.specs = std::move(*funSpecs); + } + + if (funArgs) + { + funIdentifier.args = std::move(*funArgs); + } + + tokenStack.resize(pendingStack.size()); + tokenStack.emplace_back( + std::in_place_type, + std::move(funType)); + + return true; + } + + inline bool try_reduce_as_function_type(TokenStack& tokenStack) + { + return try_reduce_as_named_function(tokenStack) + || try_reduce_as_unnamed_function(tokenStack); + } + constexpr bool try_reduce_as_function_identifier(TokenStack& tokenStack) { std::span pendingStack{tokenStack}; @@ -729,38 +911,35 @@ namespace mimicpp::printing::type::parsing } remove_suffix(pendingStack, 1u); - FunctionIdentifier funIdentifier{}; - if (auto* identifier = match_suffix(pendingStack)) + auto* funIdentifier = match_suffix(pendingStack); + if (!funIdentifier + || funIdentifier->is_function()) { - funIdentifier.identifier = std::move(*identifier); - remove_suffix(pendingStack, 1u); + return false; } + auto& funInfo = funIdentifier->functionInfo.emplace(); + if (funSpecs) { - funIdentifier.specs = std::move(*funSpecs); + funInfo.specs = std::move(*funSpecs); } if (funArgs) { - funIdentifier.args = std::move(*funArgs); + funInfo.args = std::move(*funArgs); } tokenStack.resize(pendingStack.size()); - // There may be something similar to a return type in front, which hasn't been reduced yet. - try_reduce_as_type(tokenStack); - - tokenStack.emplace_back(std::move(funIdentifier)); - return true; } inline bool try_reduce_as_type(TokenStack& tokenStack) { - bool const isFunction = token::try_reduce_as_function_identifier(tokenStack) - && token::try_reduce_as_function_type(tokenStack); - return isFunction + try_reduce_as_function_identifier(tokenStack); + + return try_reduce_as_function_type(tokenStack) || try_reduce_as_regular_type(tokenStack); } @@ -817,7 +996,7 @@ namespace mimicpp::printing::type::parsing next = m_Lexer.next()) { std::visit( - [&](auto const& tokenClass) { handle_lexer_token(tokenClass); }, + [&](auto const& tokenClass) { handle_lexer_token(next.content, tokenClass); }, next.classification); } @@ -864,29 +1043,33 @@ namespace mimicpp::printing::type::parsing return unwrap_visitor(m_Visitor); } - constexpr void handle_lexer_token([[maybe_unused]] lexing::end const& end) + constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, [[maybe_unused]] lexing::end const& end) { util::unreachable(); } - constexpr void handle_lexer_token([[maybe_unused]] lexing::space const& space) + constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, [[maybe_unused]] lexing::space const& space) { // In some cases, a space after an identifier carries semantic meaning. // I.e. consider these two type-names: `void ()` and `foo()`, - // where the former is a type returning void and the latter is a function named `foo`. - if (is_suffix_of(m_TokenStack)) + // where the former is a type returning `void` and the latter is a function named `foo`. + // In fact keep all spaces directly before all opening tokens. + if (auto const* nextOp = std::get_if(&m_Lexer.peek().classification); + nextOp + && util::contains(std::array{openingParens, openingAngle, openingCurly, openingSquare, backtick}, *nextOp)) { + token::try_reduce_as_type(m_TokenStack); m_TokenStack.emplace_back(token::Space{}); } } - constexpr void handle_lexer_token(lexing::identifier const& identifier) + constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, lexing::identifier const& identifier) { m_TokenStack.emplace_back( token::Identifier{.content = identifier.content}); } - constexpr void handle_lexer_token(lexing::keyword const& keyword) + constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, lexing::keyword const& keyword) { if (constKeyword == keyword) { @@ -902,7 +1085,7 @@ namespace mimicpp::printing::type::parsing } } - constexpr void handle_lexer_token(lexing::operator_or_punctuator const& token) + constexpr void handle_lexer_token(StringViewT const content, lexing::operator_or_punctuator const& token) { if (scopeResolution == token) { @@ -914,7 +1097,9 @@ namespace mimicpp::printing::type::parsing token::try_reduce_as_type(m_TokenStack) && token::try_reduce_as_arg_sequence(m_TokenStack); - m_TokenStack.emplace_back(token::ArgSeparator{}); + m_TokenStack.emplace_back( + std::in_place_type, + content); } else if (lvalueRef == token) { @@ -930,26 +1115,69 @@ namespace mimicpp::printing::type::parsing } else if (openingAngle == token) { - m_TokenStack.emplace_back(token::OpeningAngle{}); + // Handle something like `{...}`. + // ^ + token::try_reduce_as_placeholder_identifier(m_TokenStack); + + m_TokenStack.emplace_back( + std::in_place_type, + content); } else if (closingAngle == token) { token::try_reduce_as_type(m_TokenStack) && token::try_reduce_as_arg_sequence(m_TokenStack); - m_TokenStack.emplace_back(token::ClosingAngle{}); - token::try_reduce_as_template_identifier(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(token::OpeningParens{}); + // Handle something like `void {...}(Args)`. + // ^ + token::try_reduce_as_placeholder_identifier(m_TokenStack); + + m_TokenStack.emplace_back( + std::in_place_type, + content); } else if (closingParens == token) { token::try_reduce_as_type(m_TokenStack) && token::try_reduce_as_arg_sequence(m_TokenStack); - m_TokenStack.emplace_back(token::ClosingParens{}); + m_TokenStack.emplace_back( + std::in_place_type, + content); + } + 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) + { + m_TokenStack.emplace_back( + std::in_place_type, + content); + token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); } } }; diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 0213e717f..b3a6c5ede 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -877,3 +877,197 @@ TEST_CASE( parser(); } } + +TEST_CASE( + "parsing::NameParser detects placeholders.", + "[print][print::type]") +{ + StringT const placeholder = GENERATE( + "{placeholder}", + "{place holder}", + "{place-holder}", + /*"(placeholder)", + "(place holder)", + "(place-holder)",*/ + "", + "", + "", + "`placeholder'", + "`place holder'", + "`place-holder'"); + + VisitorMock visitor{}; + ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("Plain placeholders are detected.") + { + StringViewT 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("Templated placeholders are detected.") + { + StringT 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(); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("Qualified placeholders are detected.") + { + StringT 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("Scoped placeholders are detected.") + { + StringT 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("Placeholder return types are detected.") + { + StringT const input = placeholder + " foo()"; + CAPTURE(input); + + sequence += visitor.begin_type.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(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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("Functions with placeholder names are detected.") + { + StringT const returnType = GENERATE("", "void"); + StringT const prefix = returnType + (returnType.empty() ? "" : " "); + StringT const input = prefix + placeholder + "()"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("Functions with placeholder scoped names are detected.") + { + StringT const returnType = GENERATE("", "void"); + StringT const prefix = returnType + (returnType.empty() ? "" : " "); + StringT const input = prefix + placeholder + "::foo()"; + CAPTURE(input); + + sequence += visitor.begin_type.expect_call(); + 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } +} From f4f56bcadc92b727305134ea6edecb8dd5535022 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 15 Apr 2025 23:06:33 +0200 Subject: [PATCH 021/130] refactor: overhaul function handling and introduce token::End as end-state --- include/mimic++/printing/type/NameParser.hpp | 390 +++++++++++-------- 1 file changed, 222 insertions(+), 168 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 7c3631854..053d830af 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -281,126 +281,89 @@ namespace mimicpp::printing::type::parsing } }; - class ScopeSequence + class FunctionIdentifier { public: - std::vector scopes{}; + Identifier identifier; + ArgSequence args{}; + Specs specs{}; template constexpr void operator()(Visitor& visitor) const { - MIMICPP_ASSERT(!scopes.empty(), "Empty scope-sequence is not allowed."); - auto& unwrapped = unwrap_visitor(visitor); - for (auto const& scope : scopes) - { - unwrapped.begin_scope(); - const bool isFunction = scope.is_function(); - if (isFunction) - { - unwrapped.begin_function(); - } - - std::invoke(scope, unwrapped); - - if (isFunction) - { - unwrapped.end_function(); - } - unwrapped.end_scope(); - } + std::invoke(identifier, unwrapped); + args.handle_as_function_args(unwrapped); + std::invoke(specs, unwrapped); } }; - class RegularType + class ScopeSequence { public: - std::optional scopes{}; - Identifier identifier; - Specs specs{}; + using Scope = std::variant; + std::vector scopes{}; template constexpr void operator()(Visitor& visitor) const { + MIMICPP_ASSERT(!scopes.empty(), "Empty scope-sequence is not allowed."); + auto& unwrapped = unwrap_visitor(visitor); - if (scopes) + for (auto const& scope : scopes) { - std::invoke(*scopes, unwrapped); + unwrapped.begin_scope(); + std::visit( + [&](auto const& id) { handle_scope(unwrapped, id); }, + scope); + unwrapped.end_scope(); } + } - std::invoke(identifier, unwrapped); - std::invoke(specs, unwrapped); + 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 FunctionType + class RegularType { public: - std::shared_ptr returnType{}; std::optional scopes{}; - Identifier identifier{}; + Identifier identifier; + Specs specs{}; template - void operator()(Visitor& visitor) const + constexpr void operator()(Visitor& visitor) const { - MIMICPP_ASSERT(identifier.is_function(), "Identifier must denote a function."); - 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); } - // Handle identifier manually, as it requires that the top-level name is not empty, which may be - // violated when an actual function-type is provided. - - if (auto const& name = identifier.content; - !name.empty()) - { - unwrapped.add_identifier(name); - } - - if (auto const& templateArgs = identifier.templateArgs) - { - templateArgs->handle_as_template_args(unwrapped); - } - - auto const& functionInfo = *identifier.functionInfo; - functionInfo.args.handle_as_function_args(unwrapped); - std::invoke(functionInfo.specs, unwrapped); - - unwrapped.end_function(); - } - - private: - template - static constexpr void handle_description([[maybe_unused]] Visitor& visitor, [[maybe_unused]] std::monostate const state) - { - } - - template - static constexpr void handle_description(Visitor& visitor, Description const& description) - { - std::invoke(description, visitor); + std::invoke(identifier, unwrapped); + std::invoke(specs, unwrapped); } }; class Type { public: - using State = std::variant; + using State = std::variant; State m_State; template @@ -461,6 +424,89 @@ namespace mimicpp::printing::type::parsing std::invoke(*this, unwrapped); unwrapped.end_function_args(); } + + class FunctionType + { + public: + std::shared_ptr returnType{}; + ArgSequence args{}; + Specs specs{}; + + template + void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_type(); + unwrapped.begin_function(); + + if (returnType) + { + unwrapped.begin_return_type(); + std::invoke(*returnType, visitor); + unwrapped.end_return_type(); + } + + args.handle_as_function_args(unwrapped); + std::invoke(specs, unwrapped); + + unwrapped.end_function(); + unwrapped.end_type(); + } + }; + + 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_type(); + 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(); + unwrapped.end_type(); + } + }; + + class End + { + public: + using State = std::variant; + State state{}; + + template + constexpr void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin(); + std::visit( + [&](auto const& s) { std::invoke(s, unwrapped); }, + state); + unwrapped.end(); + } + }; } using Token = std::variant< @@ -476,10 +522,12 @@ namespace mimicpp::printing::type::parsing token::ClosingSingleQuote, token::Identifier, + token::FunctionIdentifier, token::ScopeSequence, token::ArgSequence, token::Specs, - token::Type>; + token::Type, + token::End>; using TokenStack = std::vector; template @@ -575,23 +623,31 @@ namespace mimicpp::printing::type::parsing constexpr bool try_reduce_as_scope_sequence(TokenStack& tokenStack) { - if (!is_suffix_of(tokenStack)) + ScopeSequence::Scope scope{}; + if (auto* identifier = match_suffix(tokenStack)) + { + scope = std::move(*identifier); + } + else if (auto* funIdentifier = match_suffix(tokenStack)) + { + scope = std::move(*funIdentifier); + } + else { return false; } - auto identifier = std::get(std::move(tokenStack.back())); tokenStack.pop_back(); if (auto* sequence = match_suffix(tokenStack)) { - sequence->scopes.emplace_back(std::move(identifier)); + sequence->scopes.emplace_back(std::move(scope)); } else { tokenStack.emplace_back( ScopeSequence{ - .scopes = {std::move(identifier)}}); + .scopes = {std::move(scope)}}); } return true; @@ -655,6 +711,57 @@ namespace mimicpp::printing::type::parsing return false; } + constexpr bool try_reduce_as_function_identifier(TokenStack& tokenStack) + { + std::span pendingStack{tokenStack}; + + Specs* funSpecs{}; + if (auto* specs = match_suffix(pendingStack)) + { + funSpecs = specs; + remove_suffix(pendingStack, 1u); + } + + if (!is_suffix_of(pendingStack)) + { + return false; + } + remove_suffix(pendingStack, 1u); + + ArgSequence* funArgs{}; + if (auto* args = match_suffix(pendingStack)) + { + funArgs = args; + remove_suffix(pendingStack, 1u); + } + + if (!is_suffix_of(pendingStack)) + { + return false; + } + // Remove only OpeningParens, because the Identifier is consumed next. + remove_suffix(pendingStack, 1u); + + FunctionIdentifier funIdentifier{ + .identifier = std::get(std::move(pendingStack.back()))}; + remove_suffix(pendingStack, 1u); + + if (funSpecs) + { + funIdentifier.specs = std::move(*funSpecs); + } + + if (funArgs) + { + funIdentifier.args = std::move(*funArgs); + } + + tokenStack.resize(pendingStack.size()); + tokenStack.emplace_back(std::move(funIdentifier)); + + return true; + } + [[nodiscard]] constexpr bool is_identifier_prefix(std::span const tokenStack) noexcept { @@ -771,20 +878,23 @@ namespace mimicpp::printing::type::parsing return true; } - inline bool try_reduce_as_named_function(TokenStack& tokenStack) + inline bool try_reduce_as_type(TokenStack& tokenStack) + { + return try_reduce_as_regular_type(tokenStack); + } + + inline bool try_reduce_as_function(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; - if (auto* funIdentifier = match_suffix(pendingTokens); - funIdentifier - && funIdentifier->is_function()) + if (auto* funIdentifier = match_suffix(pendingTokens)) { - FunctionType funType{ + Function function{ .identifier = std::move(*funIdentifier)}; remove_suffix(pendingTokens, 1u); if (auto* scopes = match_suffix(pendingTokens)) { - funType.scopes = std::move(*scopes); + function.scopes = std::move(*scopes); remove_suffix(pendingTokens, 1u); } @@ -792,7 +902,7 @@ namespace mimicpp::printing::type::parsing ignore_space(pendingTokens); if (auto* returnType = match_suffix(pendingTokens)) { - funType.returnType = std::make_shared(std::move(*returnType)); + function.returnType = std::make_shared(std::move(*returnType)); remove_suffix(pendingTokens, 1u); } @@ -800,17 +910,17 @@ namespace mimicpp::printing::type::parsing std::exchange(pendingTokens, {}).size()); // There may be something similar to a return type in front, which hasn't been reduced yet. - if (!funType.returnType + if (!function.returnType && try_reduce_as_type(tokenStack)) { - funType.returnType = std::make_shared( + function.returnType = std::make_shared( std::get(std::move(tokenStack.back()))); tokenStack.pop_back(); } tokenStack.emplace_back( - std::in_place_type, - std::move(funType)); + std::in_place_type, + std::move(function)); return true; } @@ -818,7 +928,7 @@ namespace mimicpp::printing::type::parsing return false; } - inline bool try_reduce_as_unnamed_function(TokenStack& tokenStack) + inline bool try_reduce_as_function_type(TokenStack& tokenStack) { std::span pendingStack{tokenStack}; @@ -856,91 +966,45 @@ namespace mimicpp::printing::type::parsing std::move(pendingStack.back())))}; remove_suffix(pendingStack, 1u); - auto& funIdentifier = funType.identifier.functionInfo.emplace(); if (funSpecs) { - funIdentifier.specs = std::move(*funSpecs); + funType.specs = std::move(*funSpecs); } if (funArgs) { - funIdentifier.args = std::move(*funArgs); + funType.args = std::move(*funArgs); } tokenStack.resize(pendingStack.size()); tokenStack.emplace_back( - std::in_place_type, + std::in_place_type, std::move(funType)); return true; } - inline bool try_reduce_as_function_type(TokenStack& tokenStack) + inline bool try_reduce_as_end(TokenStack& tokenStack) { - return try_reduce_as_named_function(tokenStack) - || try_reduce_as_unnamed_function(tokenStack); - } - - constexpr bool try_reduce_as_function_identifier(TokenStack& tokenStack) - { - std::span pendingStack{tokenStack}; - - Specs* funSpecs{}; - if (auto* specs = match_suffix(pendingStack)) - { - funSpecs = specs; - remove_suffix(pendingStack, 1u); - } - - if (!is_suffix_of(pendingStack)) + if (is_suffix_of(tokenStack) + || try_reduce_as_function_identifier(tokenStack)) { - return false; + return try_reduce_as_function(tokenStack); } - remove_suffix(pendingStack, 1u); - ArgSequence* funArgs{}; - if (auto* args = match_suffix(pendingStack)) + if (try_reduce_as_function_type(tokenStack)) { - funArgs = args; - remove_suffix(pendingStack, 1u); - } - - if (!is_suffix_of(pendingStack)) - { - return false; - } - remove_suffix(pendingStack, 1u); - - auto* funIdentifier = match_suffix(pendingStack); - if (!funIdentifier - || funIdentifier->is_function()) - { - return false; - } - - auto& funInfo = funIdentifier->functionInfo.emplace(); - - if (funSpecs) - { - funInfo.specs = std::move(*funSpecs); + return true; } - if (funArgs) + if (is_suffix_of(tokenStack) + || try_reduce_as_type(tokenStack)) { - funInfo.args = std::move(*funArgs); + auto type = std::get(std::move(tokenStack.back())); + tokenStack.back().emplace(std::move(type)); } - tokenStack.resize(pendingStack.size()); - - return true; - } - - inline bool try_reduce_as_type(TokenStack& tokenStack) - { - try_reduce_as_function_identifier(tokenStack); - - return try_reduce_as_function_type(tokenStack) - || try_reduce_as_regular_type(tokenStack); + return false; } constexpr void add_specs(Specs::Layer newSpecs, TokenStack& tokenStack) @@ -989,8 +1053,6 @@ namespace mimicpp::printing::type::parsing constexpr void operator()() { - visitor().begin(); - for (lexing::token next = m_Lexer.next(); !std::holds_alternative(next.classification); next = m_Lexer.next()) @@ -1000,15 +1062,13 @@ namespace mimicpp::printing::type::parsing next.classification); } - try_reduce_as_type(m_TokenStack); + try_reduce_as_end(m_TokenStack); MIMICPP_ASSERT(1u == m_TokenStack.size(), "A single end-state is required."); - MIMICPP_ASSERT(std::holds_alternative(m_TokenStack.back()), "Only token::Type is allowed as end-state."); + MIMICPP_ASSERT(std::holds_alternative(m_TokenStack.back()), "Only token::End is allowed as end-state."); std::invoke( - std::get(m_TokenStack.back()), - visitor()); - - visitor().end(); + std::get(m_TokenStack.back()), + m_Visitor); } private: @@ -1037,13 +1097,7 @@ namespace mimicpp::printing::type::parsing std::vector m_TokenStack{}; - [[nodiscard]] - constexpr auto& visitor() noexcept - { - return unwrap_visitor(m_Visitor); - } - - constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, [[maybe_unused]] lexing::end const& end) + static constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, [[maybe_unused]] lexing::end const& end) { util::unreachable(); } From a8e32ec54d22be54e6b8166729aa04b65e30c1c6 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Wed, 16 Apr 2025 16:05:48 +0200 Subject: [PATCH 022/130] fix: make util::concat_array working on msvc --- include/mimic++/utilities/Algorithm.hpp | 5 ++++- test/unit-tests/utilities/Algorithm.cpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/mimic++/utilities/Algorithm.hpp b/include/mimic++/utilities/Algorithm.hpp index 6c76ff088..a66aaadd2 100644 --- a/include/mimic++/utilities/Algorithm.hpp +++ b/include/mimic++/utilities/Algorithm.hpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace mimicpp::util { @@ -237,8 +238,10 @@ namespace mimicpp::util */ template requires(... && std::same_as>) + // Not how I would like to formulate that constraint, but msvc does not accept it otherwise. + && (... && (0u <= std::tuple_size_v)) [[nodiscard]] - constexpr std::array)> concat_arrays(std::array const& first, Others const&... others) + constexpr auto concat_arrays(std::array const& first, Others const&... others) { if constexpr (0u == sizeof...(Others)) { diff --git a/test/unit-tests/utilities/Algorithm.cpp b/test/unit-tests/utilities/Algorithm.cpp index 971511ab7..0401b2009 100644 --- a/test/unit-tests/utilities/Algorithm.cpp +++ b/test/unit-tests/utilities/Algorithm.cpp @@ -255,7 +255,7 @@ TEST_CASE( { SECTION("Empty collection is supported.") { - constexpr std::vector collection{}; + std::vector const collection{}; auto const result = util::prefix_range(collection, StringViewT{"foo"}); From 29484188e79b0d1fa47b85041fc8cdae0ef00984 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Wed, 16 Apr 2025 16:06:53 +0200 Subject: [PATCH 023/130] fix: please msvc --- include/mimic++/printing/type/NameParser.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 053d830af..34c088026 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -555,8 +555,10 @@ namespace mimicpp::printing::type::parsing util::type_list{}, tokenStack.first(tokenStack.size() - 1)); } - - return true; + else + { + return true; + } } } From c8e516b3de21ce8f38be07617ddba42d6ce4fd9c Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 18 Apr 2025 13:21:56 +0200 Subject: [PATCH 024/130] fix: remove obsolete function-info symbols from parsing::token::Identifier --- include/mimic++/printing/type/NameParser.hpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 34c088026..9b84fb852 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -239,26 +239,12 @@ namespace mimicpp::printing::type::parsing StringViewT content{}; std::optional templateArgs{}; - struct FunctionInfo - { - ArgSequence args; - Specs specs{}; - }; - - std::optional functionInfo{}; - [[nodiscard]] constexpr bool is_template() const noexcept { return templateArgs.has_value(); } - [[nodiscard]] - constexpr bool is_function() const noexcept - { - return functionInfo.has_value(); - } - template constexpr void operator()(Visitor& visitor) const { @@ -272,12 +258,6 @@ namespace mimicpp::printing::type::parsing { templateArgs->handle_as_template_args(unwrapped); } - - if (functionInfo) - { - functionInfo->args.handle_as_function_args(unwrapped); - std::invoke(functionInfo->specs, unwrapped); - } } }; From b65d4c23994568655d400cb59399afedf9d2fefc Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 18 Apr 2025 13:49:23 +0200 Subject: [PATCH 025/130] fix: parsing::NameParser detects placeholders wrapped in parenthesis --- include/mimic++/printing/type/NameParser.hpp | 119 ++++++++++--------- test/unit-tests/printing/TypeNameParser.cpp | 4 +- 2 files changed, 68 insertions(+), 55 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 9b84fb852..f10ae607a 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -228,9 +228,22 @@ namespace mimicpp::printing::type::parsing template constexpr void handle_as_template_args(Visitor& visitor) const; + }; + + class FunctionArgs + { + public: + ArgSequence args{}; template - constexpr void handle_as_function_args(Visitor& visitor) const; + constexpr void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_function_args(); + std::invoke(args, unwrapped); + unwrapped.end_function_args(); + } }; class Identifier @@ -265,7 +278,7 @@ namespace mimicpp::printing::type::parsing { public: Identifier identifier; - ArgSequence args{}; + FunctionArgs args{}; Specs specs{}; template @@ -274,7 +287,7 @@ namespace mimicpp::printing::type::parsing auto& unwrapped = unwrap_visitor(visitor); std::invoke(identifier, unwrapped); - args.handle_as_function_args(unwrapped); + std::invoke(args, unwrapped); std::invoke(specs, unwrapped); } }; @@ -395,21 +408,11 @@ namespace mimicpp::printing::type::parsing unwrapped.end_template_args(); } - template - constexpr void ArgSequence::handle_as_function_args(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - unwrapped.begin_function_args(); - std::invoke(*this, unwrapped); - unwrapped.end_function_args(); - } - class FunctionType { public: std::shared_ptr returnType{}; - ArgSequence args{}; + FunctionArgs args{}; Specs specs{}; template @@ -427,7 +430,7 @@ namespace mimicpp::printing::type::parsing unwrapped.end_return_type(); } - args.handle_as_function_args(unwrapped); + std::invoke(args, unwrapped); std::invoke(specs, unwrapped); unwrapped.end_function(); @@ -505,6 +508,7 @@ namespace mimicpp::printing::type::parsing token::FunctionIdentifier, token::ScopeSequence, token::ArgSequence, + token::FunctionArgs, token::Specs, token::Type, token::End>; @@ -693,51 +697,67 @@ namespace mimicpp::printing::type::parsing return false; } - constexpr bool try_reduce_as_function_identifier(TokenStack& tokenStack) + constexpr bool try_reduce_as_function_args(TokenStack& tokenStack) { - std::span pendingStack{tokenStack}; + std::span pendingTokens{tokenStack}; + if (!is_suffix_of(pendingTokens)) + { + return false; + } + remove_suffix(pendingTokens, 1u); - Specs* funSpecs{}; - if (auto* specs = match_suffix(pendingStack)) + auto* args = match_suffix(pendingTokens); + if (args) { - funSpecs = specs; - remove_suffix(pendingStack, 1u); + remove_suffix(pendingTokens, 1u); } - if (!is_suffix_of(pendingStack)) + if (!is_suffix_of(pendingTokens)) { return false; } - remove_suffix(pendingStack, 1u); + remove_suffix(pendingTokens, 1u); + + FunctionArgs funArgs{}; + if (args) + { + funArgs.args = std::move(*args); + } + + tokenStack.resize(pendingTokens.size()); + tokenStack.emplace_back(std::move(funArgs)); + + return true; + } + + constexpr bool try_reduce_as_function_identifier(TokenStack& tokenStack) + { + std::span pendingStack{tokenStack}; - ArgSequence* funArgs{}; - if (auto* args = match_suffix(pendingStack)) + Specs* funSpecs{}; + if (auto* specs = match_suffix(pendingStack)) { - funArgs = args; + funSpecs = specs; remove_suffix(pendingStack, 1u); } - if (!is_suffix_of(pendingStack)) + std::optional tokens = match_suffix(pendingStack); + if (!tokens) { return false; } - // Remove only OpeningParens, because the Identifier is consumed next. - remove_suffix(pendingStack, 1u); + remove_suffix(pendingStack, 2u); + auto& [identifier, funArgs] = *tokens; FunctionIdentifier funIdentifier{ - .identifier = std::get(std::move(pendingStack.back()))}; - remove_suffix(pendingStack, 1u); + .identifier = std::move(identifier), + .args = std::move(funArgs)}; if (funSpecs) { funIdentifier.specs = std::move(*funSpecs); } - if (funArgs) - { - funIdentifier.args = std::move(*funArgs); - } - tokenStack.resize(pendingStack.size()); tokenStack.emplace_back(std::move(funIdentifier)); @@ -921,31 +941,26 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingStack, 1u); } - if (!is_suffix_of(pendingStack)) + FunctionArgs* funArgs = match_suffix(pendingStack); + if (!funArgs) { return false; } remove_suffix(pendingStack, 1u); - ArgSequence* funArgs{}; - if (auto* args = match_suffix(pendingStack)) - { - funArgs = args; - remove_suffix(pendingStack, 1u); - } - // The space is required, because the return type will always be spaced away from the parens. - if (!is_suffix_of(pendingStack)) + if (!is_suffix_of(pendingStack)) { return false; } - // Remove just the space and opening-parens, as we need to consume the type next. - remove_suffix(pendingStack, 2u); + // Remove just the space, as we need to consume the type next. + remove_suffix(pendingStack, 1u); FunctionType funType{ .returnType = std::make_shared( std::get( - std::move(pendingStack.back())))}; + std::move(pendingStack.back()))), + .args = std::move(*funArgs)}; remove_suffix(pendingStack, 1u); if (funSpecs) @@ -953,11 +968,6 @@ namespace mimicpp::printing::type::parsing funType.specs = std::move(*funSpecs); } - if (funArgs) - { - funType.args = std::move(*funArgs); - } - tokenStack.resize(pendingStack.size()); tokenStack.emplace_back( std::in_place_type, @@ -1188,6 +1198,9 @@ namespace mimicpp::printing::type::parsing m_TokenStack.emplace_back( std::in_place_type, content); + + token::try_reduce_as_function_args(m_TokenStack) + || token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); } else if (openingCurly == token) { diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index b3a6c5ede..d918d2e90 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -886,9 +886,9 @@ TEST_CASE( "{placeholder}", "{place holder}", "{place-holder}", - /*"(placeholder)", + //"(placeholder)", this will never be supported as we can not reliably distinguish that from FunctionArgs "(place holder)", - "(place-holder)",*/ + "(place-holder)", "", "", "", From 26a5f60766cdfd1a0c09117fc80dc277c8429898 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 18 Apr 2025 14:25:33 +0200 Subject: [PATCH 026/130] fix: remove constexpr from try_reduce_as_scope_sequence and thus pleasing libc++ --- include/mimic++/printing/type/NameParser.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index f10ae607a..8e6548738 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -607,7 +607,7 @@ namespace mimicpp::printing::type::parsing } } - constexpr bool try_reduce_as_scope_sequence(TokenStack& tokenStack) + inline bool try_reduce_as_scope_sequence(TokenStack& tokenStack) { ScopeSequence::Scope scope{}; if (auto* identifier = match_suffix(tokenStack)) From 06d4320ac3d56227851eb48769808a9660003c5c Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 18 Apr 2025 14:56:45 +0200 Subject: [PATCH 027/130] test: remove constexpr from test result, so that they are actually executed at runtime --- test/unit-tests/utilities/Algorithm.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit-tests/utilities/Algorithm.cpp b/test/unit-tests/utilities/Algorithm.cpp index 0401b2009..0f22b1795 100644 --- a/test/unit-tests/utilities/Algorithm.cpp +++ b/test/unit-tests/utilities/Algorithm.cpp @@ -321,7 +321,7 @@ TEST_CASE( constexpr std::array first{42, 1337}; constexpr std::array second{-42, -1337, 42}; - constexpr auto result = util::concat_arrays(first, second); + auto const result = util::concat_arrays(first, second); STATIC_CHECK(std::same_as>); CHECK_THAT( @@ -334,7 +334,7 @@ TEST_CASE( constexpr std::array first{42, 1337}; constexpr std::array second{-42, -1337, 42}; - constexpr auto result = util::concat_arrays(first, second, first); + auto const result = util::concat_arrays(first, second, first); STATIC_CHECK(std::same_as>); CHECK_THAT( @@ -346,7 +346,7 @@ TEST_CASE( { constexpr std::array source{}; - constexpr auto result = util::concat_arrays(source, source); + auto const result = util::concat_arrays(source, source); STATIC_CHECK(std::same_as>); CHECK_THAT( From 9adba18e4ed183c095a66bcaf40f41764dab6ca5 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 18 Apr 2025 15:27:34 +0200 Subject: [PATCH 028/130] test: remove constexpr from test input, so that they are actually executed at runtime --- test/unit-tests/utilities/Algorithm.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/unit-tests/utilities/Algorithm.cpp b/test/unit-tests/utilities/Algorithm.cpp index 0f22b1795..26c2639d6 100644 --- a/test/unit-tests/utilities/Algorithm.cpp +++ b/test/unit-tests/utilities/Algorithm.cpp @@ -306,9 +306,9 @@ TEST_CASE( { SECTION("Single array is supported.") { - constexpr std::array source{42, 1337}; + std::array const source{42, 1337}; - constexpr auto result = util::concat_arrays(source); + auto const result = util::concat_arrays(source); STATIC_CHECK(std::same_as>); CHECK_THAT( @@ -318,8 +318,8 @@ TEST_CASE( SECTION("Two arrays are supported.") { - constexpr std::array first{42, 1337}; - constexpr std::array second{-42, -1337, 42}; + std::array const first{42, 1337}; + std::array const second{-42, -1337, 42}; auto const result = util::concat_arrays(first, second); STATIC_CHECK(std::same_as>); @@ -331,8 +331,8 @@ TEST_CASE( SECTION("Arbitrary amount of arrays are supported.") { - constexpr std::array first{42, 1337}; - constexpr std::array second{-42, -1337, 42}; + std::array const first{42, 1337}; + std::array const second{-42, -1337, 42}; auto const result = util::concat_arrays(first, second, first); STATIC_CHECK(std::same_as>); @@ -344,7 +344,7 @@ TEST_CASE( SECTION("Empty arrays are supported.") { - constexpr std::array source{}; + std::array const source{}; auto const result = util::concat_arrays(source, source); STATIC_CHECK(std::same_as>); From 79171b28d7c66c17d7e00a4f76313780ef5407f3 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 18 Apr 2025 16:28:43 +0200 Subject: [PATCH 029/130] feat: add FunctionContext token --- include/mimic++/printing/type/NameParser.hpp | 136 ++++++++++--------- 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 8e6548738..aeb316b51 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -274,10 +274,9 @@ namespace mimicpp::printing::type::parsing } }; - class FunctionIdentifier + class FunctionContext { public: - Identifier identifier; FunctionArgs args{}; Specs specs{}; @@ -286,12 +285,27 @@ namespace mimicpp::printing::type::parsing { auto& unwrapped = unwrap_visitor(visitor); - std::invoke(identifier, unwrapped); std::invoke(args, unwrapped); std::invoke(specs, unwrapped); } }; + 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: @@ -411,9 +425,8 @@ namespace mimicpp::printing::type::parsing class FunctionType { public: - std::shared_ptr returnType{}; - FunctionArgs args{}; - Specs specs{}; + Type returnType{}; + FunctionContext context{}; template void operator()(Visitor& visitor) const @@ -423,15 +436,11 @@ namespace mimicpp::printing::type::parsing unwrapped.begin_type(); unwrapped.begin_function(); - if (returnType) - { - unwrapped.begin_return_type(); - std::invoke(*returnType, visitor); - unwrapped.end_return_type(); - } + unwrapped.begin_return_type(); + std::invoke(returnType, visitor); + unwrapped.end_return_type(); - std::invoke(args, unwrapped); - std::invoke(specs, unwrapped); + std::invoke(context, unwrapped); unwrapped.end_function(); unwrapped.end_type(); @@ -509,6 +518,7 @@ namespace mimicpp::printing::type::parsing token::ScopeSequence, token::ArgSequence, token::FunctionArgs, + token::FunctionContext, token::Specs, token::Type, token::End>; @@ -730,7 +740,7 @@ namespace mimicpp::printing::type::parsing return true; } - constexpr bool try_reduce_as_function_identifier(TokenStack& tokenStack) + constexpr bool try_reduce_as_function_context(TokenStack& tokenStack) { std::span pendingStack{tokenStack}; @@ -741,27 +751,43 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingStack, 1u); } - std::optional tokens = match_suffix(pendingStack); - if (!tokens) + if (auto* funArgs = match_suffix(pendingStack)) { - return false; + remove_suffix(pendingStack, 1u); + + FunctionContext funContext{ + .args = std::move(*funArgs)}; + + if (funSpecs) + { + funContext.specs = std::move(*funSpecs); + } + + tokenStack.resize(pendingStack.size()); + tokenStack.emplace_back(std::move(funContext)); + + return true; } - remove_suffix(pendingStack, 2u); - auto& [identifier, funArgs] = *tokens; - FunctionIdentifier funIdentifier{ - .identifier = std::move(identifier), - .args = std::move(funArgs)}; + return false; + } - if (funSpecs) + constexpr bool try_reduce_as_function_identifier(TokenStack& tokenStack) + { + if (std::optional suffix = match_suffix(tokenStack)) { - funIdentifier.specs = std::move(*funSpecs); - } + auto& [identifier, funCtx] = *suffix; + FunctionIdentifier funIdentifier{ + .identifier = std::move(identifier), + .context = std::move(funCtx)}; - tokenStack.resize(pendingStack.size()); - tokenStack.emplace_back(std::move(funIdentifier)); + tokenStack.pop_back(); + tokenStack.back() = std::move(funIdentifier); - return true; + return true; + } + + return false; } [[nodiscard]] @@ -930,54 +956,29 @@ namespace mimicpp::printing::type::parsing return false; } - inline bool try_reduce_as_function_type(TokenStack& tokenStack) + constexpr bool try_reduce_as_function_type(TokenStack& tokenStack) { - std::span pendingStack{tokenStack}; - - Specs* funSpecs{}; - if (auto* specs = match_suffix(pendingStack)) - { - funSpecs = specs; - remove_suffix(pendingStack, 1u); - } - - FunctionArgs* funArgs = match_suffix(pendingStack); - if (!funArgs) - { - return false; - } - remove_suffix(pendingStack, 1u); - // The space is required, because the return type will always be spaced away from the parens. - if (!is_suffix_of(pendingStack)) + if (std::optional suffix = match_suffix(tokenStack)) { - return false; - } - // Remove just the space, as we need to consume the type next. - remove_suffix(pendingStack, 1u); + auto& [returnType, space, funCtx] = *suffix; + FunctionType funType{ + .returnType = std::move(returnType), + .context = std::move(funCtx)}; - FunctionType funType{ - .returnType = std::make_shared( - std::get( - std::move(pendingStack.back()))), - .args = std::move(*funArgs)}; - remove_suffix(pendingStack, 1u); + tokenStack.resize(tokenStack.size() - 2); + tokenStack.back().emplace(std::move(funType)); - if (funSpecs) - { - funType.specs = std::move(*funSpecs); + return true; } - tokenStack.resize(pendingStack.size()); - tokenStack.emplace_back( - std::in_place_type, - std::move(funType)); - - return true; + return false; } inline bool try_reduce_as_end(TokenStack& tokenStack) { + try_reduce_as_function_context(tokenStack); + if (is_suffix_of(tokenStack) || try_reduce_as_function_identifier(tokenStack)) { @@ -1135,7 +1136,8 @@ namespace mimicpp::printing::type::parsing { if (scopeResolution == token) { - token::try_reduce_as_function_identifier(m_TokenStack); + token::try_reduce_as_function_context(m_TokenStack) + && token::try_reduce_as_function_identifier(m_TokenStack); token::try_reduce_as_scope_sequence(m_TokenStack); } else if (commaSeparator == token) From 5829a356851386c7c7b75c3a61daa792c4b7b2cd Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 18 Apr 2025 17:37:38 +0200 Subject: [PATCH 030/130] feat: add `unrecognized` callback for reporting errors in parsing::NameParser --- include/mimic++/printing/type/NameParser.hpp | 18 +++++++++++++----- test/unit-tests/printing/TypeNameParser.cpp | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index aeb316b51..b685c87fc 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -23,6 +23,8 @@ namespace mimicpp::printing::type::parsing template concept parser_visitor = std::movable && requires(std::unwrap_reference_t visitor, StringViewT content) { + visitor.unrecognized(content); + visitor.begin(); visitor.end(); @@ -1057,11 +1059,17 @@ namespace mimicpp::printing::type::parsing try_reduce_as_end(m_TokenStack); - MIMICPP_ASSERT(1u == m_TokenStack.size(), "A single end-state is required."); - MIMICPP_ASSERT(std::holds_alternative(m_TokenStack.back()), "Only token::End is allowed as end-state."); - std::invoke( - std::get(m_TokenStack.back()), - m_Visitor); + if (1u == m_TokenStack.size() + && std::holds_alternative(m_TokenStack.back())) + { + std::invoke( + std::get(m_TokenStack.back()), + m_Visitor); + } + else + { + unwrap_visitor(m_Visitor).unrecognized(m_Content); + } } private: diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index d918d2e90..15b5ef52b 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -13,6 +13,8 @@ namespace { struct VisitorMock { + Mock unrecognized{{.name = "VisitorMock::unrecognized"}}; + Mock begin{{.name = "VisitorMock::begin"}}; Mock end{{.name = "VisitorMock::end"}}; @@ -80,6 +82,20 @@ namespace }; } +TEST_CASE( + "parsing::NameParser rejects unrecognizable input.", + "[print][print::type]") +{ + StringViewT constexpr input{"Hello, World!"}; + + VisitorMock visitor{}; + + SCOPED_EXP visitor.unrecognized.expect_call("Hello, World!"); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); +} + TEST_CASE( "parsing::NameParser detects identifiers.", "[print][print::type]") From 830a0ba6c255a8470c797ee1a0df6b339fc89ffc Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 18 Apr 2025 18:07:31 +0200 Subject: [PATCH 031/130] feat: parsing::NameParser detects simple function-ptrs --- include/mimic++/printing/type/NameParser.hpp | 122 +++++++- test/unit-tests/printing/TypeNameParser.cpp | 302 +++++++++++++++++++ 2 files changed, 419 insertions(+), 5 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index b685c87fc..8c730c890 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -53,6 +53,9 @@ namespace mimicpp::printing::type::parsing visitor.end_return_type(); visitor.begin_function_args(); visitor.end_function_args(); + + visitor.begin_function_ptr(); + visitor.end_function_ptr(); }; template @@ -369,10 +372,54 @@ namespace mimicpp::printing::type::parsing } }; + class FunctionPtr + { + public: + std::optional scopes{}; + Specs specs{}; + std::shared_ptr nested{}; + + template + constexpr void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_function_ptr(); + if (scopes) + { + std::invoke(*scopes, unwrapped); + } + + std::invoke(specs, unwrapped); + unwrapped.end_function_ptr(); + } + }; + + class FunctionPtrType + { + public: + std::shared_ptr returnType{}; + FunctionPtr ptr{}; + FunctionContext context{}; + + template + constexpr void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_return_type(); + std::invoke(*returnType, visitor); + unwrapped.end_return_type(); + + std::invoke(ptr, unwrapped); + std::invoke(context, unwrapped); + } + }; + class Type { public: - using State = std::variant; + using State = std::variant; State m_State; template @@ -521,6 +568,7 @@ namespace mimicpp::printing::type::parsing token::ArgSequence, token::FunctionArgs, token::FunctionContext, + token::FunctionPtr, token::Specs, token::Type, token::End>; @@ -856,6 +904,67 @@ namespace mimicpp::printing::type::parsing return false; } + inline bool try_reduce_as_function_ptr(TokenStack& tokenStack) + { + std::span pendingTokens{tokenStack}; + if (std::optional suffix = match_suffix(pendingTokens)) + { + remove_suffix(pendingTokens, 2u); + auto& [specs, closing] = *suffix; + if (!specs.has_ptr()) + { + return false; + } + + ScopeSequence* scopeSeq = match_suffix(pendingTokens); + if (match_suffix(pendingTokens)) + { + 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); + } + + tokenStack.resize(pendingTokens.size()); + tokenStack.emplace_back(std::move(funPtr)); + + return true; + } + + return false; + } + + inline bool try_reduce_as_function_ptr_type(TokenStack& tokenStack) + { + std::span pendingTokens{tokenStack}; + if (std::optional suffix = match_suffix(tokenStack)) + { + auto& [returnType, space, ptr, ctx] = *suffix; + + ignore_space(pendingTokens); + FunctionPtrType ptrType{ + .returnType = std::make_shared(std::move(returnType)), + .ptr = std::move(ptr), + .context = std::move(ctx)}; + + tokenStack.resize(tokenStack.size() - 3); + tokenStack.back().emplace(std::move(ptrType)); + + return true; + } + + return false; + } + constexpr bool try_reduce_as_regular_type(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; @@ -867,7 +976,6 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } - // ignore_space(pendingTokens); if (!is_suffix_of(pendingTokens)) { return false; @@ -910,7 +1018,8 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_type(TokenStack& tokenStack) { - return try_reduce_as_regular_type(tokenStack); + return try_reduce_as_function_ptr_type(tokenStack) + || try_reduce_as_regular_type(tokenStack); } inline bool try_reduce_as_function(TokenStack& tokenStack) @@ -1040,9 +1149,10 @@ namespace mimicpp::printing::type::parsing { public: [[nodiscard]] - explicit constexpr NameParser(Visitor visitor, StringViewT content) noexcept(std::is_nothrow_move_constructible_v) + explicit constexpr NameParser(Visitor visitor, StringViewT const& content) noexcept(std::is_nothrow_move_constructible_v) : m_Visitor{std::move(visitor)}, - m_Lexer{std::move(content)} + m_Content{content}, + m_Lexer{content} { } @@ -1094,6 +1204,7 @@ namespace mimicpp::printing::type::parsing static constexpr lexing::keyword noexceptKeyword{"noexcept"}; Visitor m_Visitor; + StringViewT m_Content; lexing::NameLexer m_Lexer; std::vector m_TokenStack{}; @@ -1210,6 +1321,7 @@ namespace mimicpp::printing::type::parsing content); token::try_reduce_as_function_args(m_TokenStack) + || token::try_reduce_as_function_ptr(m_TokenStack) || token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); } else if (openingCurly == token) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 15b5ef52b..57dd9b8ca 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -37,6 +37,9 @@ namespace Mock begin_function_args{{.name = "VisitorMock::begin_function_args"}}; Mock end_function_args{{.name = "VisitorMock::end_function_args"}}; + Mock begin_function_ptr{{.name = "VisitorMock::begin_function_ptr"}}; + Mock end_function_ptr{{.name = "VisitorMock::end_function_ptr"}}; + Mock add_const{{.name = "VisitorMock::add_const"}}; Mock add_volatile{{.name = "VisitorMock::add_volatile"}}; Mock add_noexcept{{.name = "VisitorMock::add_noexcept"}}; @@ -1087,3 +1090,302 @@ TEST_CASE( parser(); } } + +TEST_CASE( + "parsing::NameParser detects function pointers.", + "[print][print::type]") +{ + VisitorMock visitor{}; + ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_type.expect_call(); + + SECTION("When function pointer without arguments is given.") + { + StringT const input = "const std::string* volatile& (*)()"; + 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.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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When noexcept function pointer without arguments is given.") + { + StringT const input = "void (*)()noexcept"; + CAPTURE(input); + + 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.add_noexcept.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When function pointer-ref without arguments is given.") + { + StringT const input = "void (*&)()noexcept"; + CAPTURE(input); + + 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.add_noexcept.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When function pointer with arguments is given.") + { + StringT const input = "void (*)(const std::string&&, const int)"; + CAPTURE(input); + + 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(); + + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When member function pointer without arguments is given.") + { + StringT const input = "void (foo::bar::*)()"; + CAPTURE(input); + + 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When member function pointer with qualifications is given.") + { + StringT const spec = GENERATE("const", "volatile", "noexcept", "&", "&&"); + StringT const input = "void (foo::bar::*)()" + spec; + CAPTURE(input, spec); + + 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.expect_spec_call(spec); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When return-type is a function pointer.") + { + StringT const input = "void (*()) foo()"; + CAPTURE(input); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + 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(); + sequence += visitor.end_function_args.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(); + sequence += visitor.end_function_args.expect_call(); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When Function-Ptr with a function-ptr return-type is given.") + { + StringT const input = "void (* (*)(float))(int)"; + CAPTURE(input); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + 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(); + sequence += visitor.end_function_args.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(); + sequence += visitor.end_function_args.expect_call(); + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When parameter is a function pointer.") + { + StringT const input = "void foo(void (*)())"; + CAPTURE(input); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + sequence += visitor.end_return_type.expect_call(); + + sequence += visitor.add_identifier.expect_call("foo"); + sequence += visitor.begin_function_args.expect_call(); + + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.add_identifier.expect_call("void"); + 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end_return_type.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } +} From 351649029cb9f07ed90148cfb53af6b256f3f86a Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 18 Apr 2025 19:07:15 +0200 Subject: [PATCH 032/130] fix: parsing::NameParser correctly detects function-ptrs as params --- include/mimic++/printing/type/NameParser.hpp | 69 ++++++++++++-------- test/unit-tests/printing/TypeNameParser.cpp | 53 +++++++++++++-- 2 files changed, 87 insertions(+), 35 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 8c730c890..f012a434e 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -377,7 +377,7 @@ namespace mimicpp::printing::type::parsing public: std::optional scopes{}; Specs specs{}; - std::shared_ptr nested{}; + std::shared_ptr nested{}; template constexpr void operator()(Visitor& visitor) const @@ -907,50 +907,61 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_function_ptr(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; - if (std::optional suffix = match_suffix(pendingTokens)) + if (!is_suffix_of(pendingTokens)) { - remove_suffix(pendingTokens, 2u); - auto& [specs, closing] = *suffix; - if (!specs.has_ptr()) - { - return false; - } + return false; + } + remove_suffix(pendingTokens, 1u); - ScopeSequence* scopeSeq = match_suffix(pendingTokens); - if (match_suffix(pendingTokens)) - { - remove_suffix(pendingTokens, 1u); - } + auto* nestedFunPtr = match_suffix(pendingTokens); + if (nestedFunPtr) + { + remove_suffix(pendingTokens, 1u); + } - if (!is_suffix_of(pendingTokens)) - { - return false; - } + auto* specs = match_suffix(pendingTokens); + if (!specs + || !specs->has_ptr()) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + ScopeSequence* scopeSeq = match_suffix(pendingTokens); + if (match_suffix(pendingTokens)) + { remove_suffix(pendingTokens, 1u); + } - FunctionPtr funPtr{.specs = std::move(specs)}; - if (scopeSeq) - { - funPtr.scopes = std::move(*scopeSeq); - } + if (!is_suffix_of(pendingTokens)) + { + return false; + } + remove_suffix(pendingTokens, 1u); - tokenStack.resize(pendingTokens.size()); - tokenStack.emplace_back(std::move(funPtr)); + FunctionPtr funPtr{.specs = std::move(*specs)}; + if (scopeSeq) + { + funPtr.scopes = std::move(*scopeSeq); + } - return true; + if (nestedFunPtr) + { + funPtr.nested = std::make_shared(std::move(*nestedFunPtr)); } - return false; + tokenStack.resize(pendingTokens.size()); + tokenStack.emplace_back(std::move(funPtr)); + + return true; } inline bool try_reduce_as_function_ptr_type(TokenStack& tokenStack) { - std::span pendingTokens{tokenStack}; if (std::optional suffix = match_suffix(tokenStack)) { auto& [returnType, space, ptr, ctx] = *suffix; - ignore_space(pendingTokens); FunctionPtrType ptrType{ .returnType = std::make_shared(std::move(returnType)), .ptr = std::move(ptr), @@ -1313,6 +1324,8 @@ namespace mimicpp::printing::type::parsing } else if (closingParens == token) { + token::try_reduce_as_function_context(m_TokenStack); + token::try_reduce_as_type(m_TokenStack) && token::try_reduce_as_arg_sequence(m_TokenStack); diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 57dd9b8ca..eeb7e0bd0 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1297,7 +1297,7 @@ TEST_CASE( parser(); } - SECTION("When return-type is a function pointer.") + /*SECTION("When return-type is a function pointer.") { StringT const input = "void (*()) foo()"; CAPTURE(input); @@ -1355,21 +1355,60 @@ TEST_CASE( printing::type::parsing::NameParser parser{std::ref(visitor), input}; parser(); - } + }*/ - SECTION("When parameter is a function pointer.") + SECTION("When function-ptr with function-ptr parameter is given.") { - StringT const input = "void foo(void (*)())"; + StringT const input = "void (*)(void (*)())"; CAPTURE(input); 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_ptr.expect_call(); + sequence += visitor.add_ptr.expect_call(); + sequence += visitor.end_function_ptr.expect_call(); + sequence += visitor.begin_function_args.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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When parameter is a template argument.") + { + StringT const input = "foo"; + CAPTURE(input); + + sequence += visitor.add_identifier.expect_call("foo"); + + sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_return_type.expect_call(); sequence += visitor.add_identifier.expect_call("void"); sequence += visitor.end_return_type.expect_call(); @@ -1380,10 +1419,10 @@ TEST_CASE( sequence += visitor.begin_function_args.expect_call(); sequence += visitor.end_function_args.expect_call(); - sequence += visitor.end_function_args.expect_call(); + sequence += visitor.end_template_args.expect_call(); sequence += visitor.end_type.expect_call(); - sequence += visitor.end_return_type.expect_call(); + sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; parser(); From 7fcb8557b15272cec4db671ad513096acd95d944 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 18 Apr 2025 19:13:05 +0200 Subject: [PATCH 033/130] fix: parsing::NameParser correctly detects function-ptrs as template arguments --- include/mimic++/printing/type/NameParser.hpp | 4 ++-- test/unit-tests/printing/TypeNameParser.cpp | 25 +++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index f012a434e..7395617d3 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -1029,6 +1029,8 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_type(TokenStack& tokenStack) { + token::try_reduce_as_function_context(tokenStack); + return try_reduce_as_function_ptr_type(tokenStack) || try_reduce_as_regular_type(tokenStack); } @@ -1324,8 +1326,6 @@ namespace mimicpp::printing::type::parsing } else if (closingParens == token) { - token::try_reduce_as_function_context(m_TokenStack); - token::try_reduce_as_type(m_TokenStack) && token::try_reduce_as_arg_sequence(m_TokenStack); diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index eeb7e0bd0..bd63448a5 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1388,6 +1388,7 @@ TEST_CASE( sequence += visitor.end_function_ptr.expect_call(); sequence += visitor.begin_function_args.expect_call(); sequence += visitor.end_function_args.expect_call(); + sequence += visitor.end_type.expect_call(); } @@ -1409,15 +1410,23 @@ TEST_CASE( sequence += visitor.begin_template_args.expect_call(); - sequence += visitor.begin_return_type.expect_call(); - sequence += visitor.add_identifier.expect_call("void"); - sequence += visitor.end_return_type.expect_call(); + { + sequence += visitor.begin_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(); - sequence += visitor.end_function_args.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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + } sequence += visitor.end_template_args.expect_call(); From ccf9870b39f8fbc6db94e87ca059028b285a0ad4 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 22 Apr 2025 12:39:10 +0200 Subject: [PATCH 034/130] feat: parsing::NameParser handles function-ptr return-types --- include/mimic++/printing/type/NameParser.hpp | 117 ++++++++++++++----- test/unit-tests/printing/TypeNameParser.cpp | 83 ++++++++----- 2 files changed, 145 insertions(+), 55 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 7395617d3..81a7c7aa6 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -377,41 +377,44 @@ namespace mimicpp::printing::type::parsing public: std::optional scopes{}; Specs specs{}; - std::shared_ptr nested{}; - template - constexpr void operator()(Visitor& visitor) const + struct NestedInfo { - auto& unwrapped = unwrap_visitor(visitor); - - unwrapped.begin_function_ptr(); - if (scopes) - { - std::invoke(*scopes, unwrapped); - } + std::shared_ptr ptr{}; + FunctionContext ctx{}; + }; - std::invoke(specs, unwrapped); - unwrapped.end_function_ptr(); - } + std::optional nested{}; }; class FunctionPtrType { public: std::shared_ptr returnType{}; - FunctionPtr ptr{}; + std::optional scopes{}; + Specs specs{}; FunctionContext context{}; template constexpr void operator()(Visitor& visitor) const { + MIMICPP_ASSERT(returnType, "Return type is mandatory for function-ptrs."); + auto& unwrapped = unwrap_visitor(visitor); unwrapped.begin_return_type(); std::invoke(*returnType, visitor); unwrapped.end_return_type(); - std::invoke(ptr, unwrapped); + unwrapped.begin_function_ptr(); + if (scopes) + { + std::invoke(*scopes, unwrapped); + } + + std::invoke(specs, unwrapped); + unwrapped.end_function_ptr(); + std::invoke(context, unwrapped); } }; @@ -913,10 +916,17 @@ namespace mimicpp::printing::type::parsing } remove_suffix(pendingTokens, 1u); - auto* nestedFunPtr = match_suffix(pendingTokens); - if (nestedFunPtr) + 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); + } } auto* specs = match_suffix(pendingTokens); @@ -927,7 +937,7 @@ namespace mimicpp::printing::type::parsing } remove_suffix(pendingTokens, 1u); - ScopeSequence* scopeSeq = match_suffix(pendingTokens); + auto* scopeSeq = match_suffix(pendingTokens); if (match_suffix(pendingTokens)) { remove_suffix(pendingTokens, 1u); @@ -945,9 +955,17 @@ namespace mimicpp::printing::type::parsing funPtr.scopes = std::move(*scopeSeq); } - if (nestedFunPtr) + if (nestedFunCtx) { - funPtr.nested = std::make_shared(std::move(*nestedFunPtr)); + 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()); @@ -956,26 +974,65 @@ namespace mimicpp::printing::type::parsing return true; } + namespace detail + { + void handled_nested_function_ptr(TokenStack& tokenStack, FunctionPtr::NestedInfo info); + } + inline bool try_reduce_as_function_ptr_type(TokenStack& tokenStack) { if (std::optional suffix = match_suffix(tokenStack)) { auto& [returnType, space, ptr, ctx] = *suffix; + std::optional nestedInfo = std::move(ptr.nested); FunctionPtrType ptrType{ .returnType = std::make_shared(std::move(returnType)), - .ptr = std::move(ptr), + .scopes = std::move(ptr.scopes), + .specs = std::move(ptr.specs), .context = std::move(ctx)}; tokenStack.resize(tokenStack.size() - 3); 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); + } + } + } + constexpr bool try_reduce_as_regular_type(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; @@ -1109,19 +1166,21 @@ namespace mimicpp::printing::type::parsing return try_reduce_as_function(tokenStack); } - if (try_reduce_as_function_type(tokenStack)) - { - return true; - } - if (is_suffix_of(tokenStack) || try_reduce_as_type(tokenStack)) { - auto type = std::get(std::move(tokenStack.back())); - tokenStack.back().emplace(std::move(type)); + // Do to some function-ptr reductions, there may be no actual `type`-token present. + // If not, it's probably a function-type. + if (auto* type = std::get_if(&tokenStack.back())) + { + tokenStack.back().emplace( + std::exchange(*type, {})); + + return true; + } } - return false; + return try_reduce_as_function_type(tokenStack); } constexpr void add_specs(Specs::Layer newSpecs, TokenStack& tokenStack) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index bd63448a5..169e0438e 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1297,31 +1297,46 @@ TEST_CASE( parser(); } - /*SECTION("When return-type is a function pointer.") + SECTION("When return-type is a function pointer.") { - StringT const input = "void (*()) foo()"; + StringT const input = "void (*(float))(int)"; CAPTURE(input); - sequence += visitor.begin_return_type.expect_call(); - sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_function.expect_call(); - sequence += visitor.begin_return_type.expect_call(); - sequence += visitor.add_identifier.expect_call("void"); - sequence += visitor.end_return_type.expect_call(); + { // handles the `void (*)(int)` return-type. + sequence += visitor.begin_return_type.expect_call(); + sequence += visitor.begin_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(); - sequence += visitor.end_function_args.expect_call(); - sequence += visitor.end_type.expect_call(); - sequence += visitor.end_return_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(); + 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.add_identifier.expect_call("foo"); sequence += visitor.begin_function_args.expect_call(); + 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_type.expect_call(); - sequence += visitor.end_return_type.expect_call(); + sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; parser(); @@ -1329,33 +1344,49 @@ TEST_CASE( SECTION("When Function-Ptr with a function-ptr return-type is given.") { - StringT const input = "void (* (*)(float))(int)"; + StringT const input = "void (*(*)(float))(int)"; CAPTURE(input); sequence += visitor.begin_return_type.expect_call(); sequence += visitor.begin_type.expect_call(); - sequence += visitor.begin_return_type.expect_call(); - sequence += visitor.add_identifier.expect_call("void"); + { // 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(); + 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(); - sequence += visitor.end_function_args.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(); + 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_return_type.expect_call(); + sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; parser(); - }*/ + } SECTION("When function-ptr with function-ptr parameter is given.") { From 14d6c1b2f08363d0227419eae2796086a30858c8 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 22 Apr 2025 12:55:19 +0200 Subject: [PATCH 035/130] fix: remove invalid constexpr --- include/mimic++/printing/type/NameParser.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 81a7c7aa6..5377dda1c 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -1137,7 +1137,7 @@ namespace mimicpp::printing::type::parsing return false; } - constexpr bool try_reduce_as_function_type(TokenStack& tokenStack) + inline bool try_reduce_as_function_type(TokenStack& tokenStack) { // The space is required, because the return type will always be spaced away from the parens. if (std::optional suffix = match_suffix(tokenStack)) From ed45a7d4b66ff1ba563e9827fe58c4413b37a3e0 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 22 Apr 2025 13:16:48 +0200 Subject: [PATCH 036/130] chore: function and function-type tokens do not emit begin/end_type callbacks --- include/mimic++/printing/type/NameParser.hpp | 4 --- test/unit-tests/printing/TypeNameParser.cpp | 38 ++++++++++---------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 5377dda1c..c8ea444a0 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -485,7 +485,6 @@ namespace mimicpp::printing::type::parsing { auto& unwrapped = unwrap_visitor(visitor); - unwrapped.begin_type(); unwrapped.begin_function(); unwrapped.begin_return_type(); @@ -495,7 +494,6 @@ namespace mimicpp::printing::type::parsing std::invoke(context, unwrapped); unwrapped.end_function(); - unwrapped.end_type(); } }; @@ -511,7 +509,6 @@ namespace mimicpp::printing::type::parsing { auto& unwrapped = unwrap_visitor(visitor); - unwrapped.begin_type(); unwrapped.begin_function(); if (returnType) @@ -529,7 +526,6 @@ namespace mimicpp::printing::type::parsing std::invoke(identifier, unwrapped); unwrapped.end_function(); - unwrapped.end_type(); } }; diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 169e0438e..0ec381f2b 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -468,7 +468,6 @@ TEST_CASE( ScopedSequence sequence{}; sequence += visitor.begin.expect_call(); - sequence += visitor.begin_type.expect_call(); sequence += visitor.begin_function.expect_call(); SECTION("When function identifier with 0 args is given.") @@ -498,7 +497,6 @@ TEST_CASE( } sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -538,7 +536,6 @@ TEST_CASE( } sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -581,7 +578,6 @@ TEST_CASE( } sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -630,7 +626,6 @@ TEST_CASE( } sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -677,7 +672,6 @@ TEST_CASE( } sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -740,7 +734,6 @@ TEST_CASE( } sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -779,7 +772,6 @@ TEST_CASE( } sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -816,7 +808,6 @@ TEST_CASE( } sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -836,7 +827,6 @@ TEST_CASE( StringT const suffix = specSpace + spec; sequence += visitor.begin.expect_call(); - sequence += visitor.begin_type.expect_call(); sequence += visitor.begin_function.expect_call(); SECTION("When function has an unqualified return-type.") @@ -859,7 +849,6 @@ TEST_CASE( } sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -889,7 +878,6 @@ TEST_CASE( } sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -1003,7 +991,6 @@ TEST_CASE( StringT const input = placeholder + " foo()"; CAPTURE(input); - sequence += visitor.begin_type.expect_call(); sequence += visitor.begin_function.expect_call(); sequence += visitor.begin_return_type.expect_call(); @@ -1017,7 +1004,6 @@ TEST_CASE( sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -1031,7 +1017,6 @@ TEST_CASE( StringT const input = prefix + placeholder + "()"; CAPTURE(input); - sequence += visitor.begin_type.expect_call(); sequence += visitor.begin_function.expect_call(); CHECKED_IF(!returnType.empty()) @@ -1048,7 +1033,6 @@ TEST_CASE( sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -1062,7 +1046,6 @@ TEST_CASE( StringT const input = prefix + placeholder + "::foo()"; CAPTURE(input); - sequence += visitor.begin_type.expect_call(); sequence += visitor.begin_function.expect_call(); CHECKED_IF(!returnType.empty()) @@ -1083,7 +1066,6 @@ TEST_CASE( sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -1099,13 +1081,14 @@ TEST_CASE( ScopedSequence sequence{}; sequence += visitor.begin.expect_call(); - sequence += visitor.begin_type.expect_call(); SECTION("When function pointer without arguments is given.") { StringT 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(); @@ -1141,6 +1124,8 @@ TEST_CASE( StringT 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"); @@ -1168,6 +1153,8 @@ TEST_CASE( StringT 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"); @@ -1196,6 +1183,8 @@ TEST_CASE( StringT 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"); @@ -1237,6 +1226,8 @@ TEST_CASE( StringT 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"); @@ -1269,6 +1260,8 @@ TEST_CASE( StringT 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"); @@ -1335,7 +1328,6 @@ TEST_CASE( sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); - sequence += visitor.end_type.expect_call(); sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; @@ -1347,6 +1339,8 @@ TEST_CASE( StringT 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(); @@ -1393,6 +1387,8 @@ TEST_CASE( StringT 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"); @@ -1437,6 +1433,8 @@ TEST_CASE( StringT 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(); From c3365bfa7a746fe34a8aefe5f0ce13b6e400ee10 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 22 Apr 2025 13:27:59 +0200 Subject: [PATCH 037/130] test: extend parsing::NameParser tests --- test/unit-tests/printing/TypeNameParser.cpp | 180 ++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 0ec381f2b..0a91b78a3 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1466,3 +1466,183 @@ TEST_CASE( parser(); } } + +TEST_CASE( + "parsing::NameParser handles arbitrarily scoped identifiers.", + "[print][print::type]") +{ + VisitorMock visitor{}; + 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 StringViewT 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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When qualified function local type is given.") + { + constexpr StringViewT 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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When nested function local type is given.") + { + constexpr StringViewT 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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When deeply nested function local type is given.") + { + constexpr StringViewT 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(); + + { + 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(); + 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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } +} From c2cade9fc053e4bbde4388fd21c8d90615217c7b Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 22 Apr 2025 15:53:12 +0200 Subject: [PATCH 038/130] feat: extend lexer keyword collection --- include/mimic++/printing/type/NameLexer.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/mimic++/printing/type/NameLexer.hpp b/include/mimic++/printing/type/NameLexer.hpp index 1b61a571f..10e651a59 100644 --- a/include/mimic++/printing/type/NameLexer.hpp +++ b/include/mimic++/printing/type/NameLexer.hpp @@ -45,7 +45,9 @@ namespace mimicpp::printing::type::lexing namespace texts { // just list the noteworthy ones here - constexpr std::array keywords = std::to_array({"const", "constexpr", "volatile", "noexcept", "operator"}); + constexpr std::array visibilityKeywords = std::to_array({"public", "protected", "private"}); + constexpr std::array specKeywords = std::to_array({"const", "constexpr", "volatile", "noexcept"}); + constexpr std::array contextKeywords = std::to_array({"operator", "struct", "class", "enum"}); constexpr std::array digraphs = std::to_array({"and", "or", "xor", "not", "bitand", "bitor", "compl", "and_eq", "or_eq", "xor_eq", "not_eq"}); constexpr std::array braceLikes = std::to_array({"{", "}", "[", "]", "(", ")", "`", "'"}); @@ -63,7 +65,9 @@ namespace mimicpp::printing::type::lexing constexpr std::array keywordCollection = std::invoke( [] { std::array collection = util::concat_arrays( - texts::keywords, + texts::visibilityKeywords, + texts::specKeywords, + texts::contextKeywords, texts::digraphs); std::ranges::sort(collection); From 521a420d1c6ea74db8d8edd72e98323616b6d549 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 22 Apr 2025 16:12:10 +0200 Subject: [PATCH 039/130] feat: parsing::NameParser supports msvc-like function-scopes --- include/mimic++/printing/type/NameParser.hpp | 96 +++++++++- test/unit-tests/printing/TypeNameParser.cpp | 175 +++++++++++++++++++ 2 files changed, 265 insertions(+), 6 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index c8ea444a0..6217b3642 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -1297,10 +1297,41 @@ namespace mimicpp::printing::type::parsing } } + [[nodiscard]] + constexpr bool keep_reserved_identifier() const noexcept + { + if (!m_TokenStack.empty() + && !is_suffix_of(m_TokenStack)) + { + return false; + } + + auto const& next = m_Lexer.peek().classification; + if (std::holds_alternative(next)) + { + return true; + } + + if (auto const* op = std::get_if(&next)) + { + return openingAngle == *op + || openingParens == *op + || scopeResolution == *op; + } + + return false; + } + constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, lexing::identifier const& identifier) { - m_TokenStack.emplace_back( - token::Identifier{.content = identifier.content}); + // Some environments add many reserved symbols (e.g. `__cdecl`). We want to filter out most of these, + // but keep those, which are actual function-names. + if (!identifier.content.starts_with("__") + || keep_reserved_identifier()) + { + m_TokenStack.emplace_back( + token::Identifier{.content = identifier.content}); + } } constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, lexing::keyword const& keyword) @@ -1413,10 +1444,63 @@ namespace mimicpp::printing::type::parsing } else if (singleQuote == token) { - m_TokenStack.emplace_back( - std::in_place_type, - content); - token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); + if (token::try_reduce_as_function_context(m_TokenStack) + && token::try_reduce_as_function_identifier(m_TokenStack)) + { + unwrap_msvc_like_function(); + } + else + { + m_TokenStack.emplace_back( + std::in_place_type, + content); + token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); + } + } + } + + void unwrap_msvc_like_function() + { + MIMICPP_ASSERT(is_suffix_of(m_TokenStack), "Invalid state."); + + auto funIdentifier = std::get(m_TokenStack.back()); + m_TokenStack.pop_back(); + + token::ScopeSequence scopes{}; + if (auto* scopeSeq = match_suffix(m_TokenStack)) + { + scopes = std::move(*scopeSeq); + m_TokenStack.pop_back(); + } + + scopes.scopes.emplace_back(std::move(funIdentifier)); + + if (is_suffix_of(m_TokenStack)) + { + m_TokenStack.pop_back(); + } + + // Ignore return-types. + if (is_suffix_of(m_TokenStack) + || token::try_reduce_as_type(m_TokenStack)) + { + m_TokenStack.pop_back(); + } + + MIMICPP_ASSERT(match_suffix(m_TokenStack), "Invalid state."); + m_TokenStack.pop_back(); + + if (auto* targetScopes = match_suffix(m_TokenStack)) + { + auto& target = targetScopes->scopes; + target.insert( + target.cend(), + std::make_move_iterator(scopes.scopes.begin()), + std::make_move_iterator(scopes.scopes.end())); + } + else + { + m_TokenStack.emplace_back(std::move(scopes)); } } }; diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 0a91b78a3..857a1dbee 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1646,3 +1646,178 @@ TEST_CASE( parser(); } } + +TEST_CASE( + "parsing::NameParser handles msvc-like function scopes.", + "[print][print::type]") +{ + VisitorMock visitor{}; + ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + sequence += visitor.begin_type.expect_call(); + + SECTION("When function local type is given.") + { + constexpr StringViewT input{"`void foo()'::my_type"}; + CAPTURE(input); + + 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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("Deeply nested function local type is given.") + { + constexpr StringViewT input{ + "`ret2 `ret1 `ret0 inner::fn0(int const)'::`my_placeholder'::middle::fn1()const'::outer::fn2()const &'::my_type"}; + CAPTURE(input); + + 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(); + 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(); + 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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When decorated function local type is given.") + { + StringViewT const visibility = GENERATE("public", "private", "protected"); + StringViewT const typeClass = GENERATE("struct", "class", "enum"); + StringT const input = StringT{typeClass} + " `" + StringT{visibility} + ": void __cdecl foo() __ptr64'::my_type"; + CAPTURE(input); + + 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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } +} From ddb329abcfc08b1a7cab0ced92e606098b6d1b19 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 22 Apr 2025 16:30:18 +0200 Subject: [PATCH 040/130] test: extend placeholder parsing test-case --- test/unit-tests/printing/TypeNameParser.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 857a1dbee..0db87174f 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -891,15 +891,19 @@ TEST_CASE( { StringT const placeholder = GENERATE( "{placeholder}", + "{__placeholder}", "{place holder}", "{place-holder}", //"(placeholder)", this will never be supported as we can not reliably distinguish that from FunctionArgs + //"(__placeholder)", "(place holder)", "(place-holder)", - "", "", + "<__placeholder>", + "", "", "`placeholder'", + "`__placeholder'", "`place holder'", "`place-holder'"); From c5b66271e9e26f5012b2ba0f7a5f91c368aca7f6 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 22 Apr 2025 16:49:19 +0200 Subject: [PATCH 041/130] test: add parsing::NameParser cases for reserved identifier handling --- include/mimic++/printing/type/NameParser.hpp | 3 +- test/unit-tests/printing/TypeNameParser.cpp | 66 ++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 6217b3642..0c2f69c74 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -1325,7 +1325,8 @@ namespace mimicpp::printing::type::parsing constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, lexing::identifier const& identifier) { // Some environments add many reserved symbols (e.g. `__cdecl`). We want to filter out most of these, - // but keep those, which are actual function-names. + // but keep those, which are actual names. + // Note: Currently reserved identifiers are only accepted if they are top-level or (template-)functions. if (!identifier.content.starts_with("__") || keep_reserved_identifier()) { diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 0db87174f..f36daeecf 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1825,3 +1825,69 @@ TEST_CASE( parser(); } } + +TEST_CASE( + "parsing::NameParser keeps meaningful reserved identifiers.", + "[print][print::type]") +{ + StringT const identifier = "__identifier"; + + VisitorMock visitor{}; + ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("Just a single identifier.") + { + StringT 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), identifier}; + parser(); + } + + SECTION("As function name.") + { + StringT 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("As template.") + { + StringT 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(); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } +} From 0d16b8c88def2ad683c6acd91979ce7c18f2eec1 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 22 Apr 2025 19:47:37 +0200 Subject: [PATCH 042/130] feat: parsing::NameParser supports simple operators --- include/mimic++/printing/type/NameParser.hpp | 138 ++++++++++++++---- test/unit-tests/printing/TypeNameParser.cpp | 143 +++++++++++++++++++ 2 files changed, 257 insertions(+), 24 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 0c2f69c74..da212b474 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -56,6 +56,9 @@ namespace mimicpp::printing::type::parsing visitor.begin_function_ptr(); visitor.end_function_ptr(); + + visitor.begin_operator_identifier(); + visitor.end_operator_identifier(); }; template @@ -254,7 +257,13 @@ namespace mimicpp::printing::type::parsing class Identifier { public: - StringViewT content{}; + struct OperatorInfo + { + StringViewT symbol{}; + }; + + using Content = std::variant; + Content content{}; std::optional templateArgs{}; [[nodiscard]] @@ -266,17 +275,36 @@ namespace mimicpp::printing::type::parsing template constexpr void operator()(Visitor& visitor) const { - MIMICPP_ASSERT(!content.empty(), "Empty identifier is not allowed."); - auto& unwrapped = unwrap_visitor(visitor); - unwrapped.add_identifier(content); + 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, StringViewT const& content) + { + MIMICPP_ASSERT(!content.empty(), "Empty identifier is not allowed."); + + visitor.add_identifier(content); + } + + template + static constexpr void handle_content(Visitor& visitor, OperatorInfo const& content) + { + MIMICPP_ASSERT(!content.symbol.empty(), "Empty symbol is not allowed."); + + visitor.begin_operator_identifier(); + visitor.add_identifier(content.symbol); + visitor.end_operator_identifier(); + } }; class FunctionContext @@ -725,35 +753,43 @@ namespace mimicpp::printing::type::parsing constexpr bool try_reduce_as_template_identifier(TokenStack& tokenStack) { - if (std::optional suffix = match_suffix(tokenStack)) + std::span pendingTokens{tokenStack}; + if (!is_suffix_of(pendingTokens)) { - auto& [id, opening, args, closing] = *suffix; - if (id.is_template()) - { - return false; - } - - id.templateArgs = std::move(args); - tokenStack.resize(tokenStack.size() - 3u); + return false; + } + remove_suffix(pendingTokens, 1u); - return true; + auto* args = match_suffix(pendingTokens); + if (args) + { + remove_suffix(pendingTokens, 1u); } - if (std::optional suffix = match_suffix(tokenStack)) + if (!is_suffix_of(pendingTokens)) { - auto& [id, opening, closing] = *suffix; - if (id.is_template()) - { - return false; - } + return false; + } + remove_suffix(pendingTokens, 1u); - id.templateArgs.emplace(); - tokenStack.resize(tokenStack.size() - 2u); + auto* id = match_suffix(pendingTokens); + if (!id + || id->is_template()) + { + return false; + } - return true; + if (args) + { + id->templateArgs = std::move(*args); + } + else + { + id->templateArgs.emplace(); } + tokenStack.resize(pendingTokens.size()); - return false; + return true; } constexpr bool try_reduce_as_function_args(TokenStack& tokenStack) @@ -1349,6 +1385,60 @@ namespace mimicpp::printing::type::parsing { token::add_specs({.isNoexcept = true}, m_TokenStack); } + else if (operatorKeyword == keyword) + { + process_simple_operator(); + } + } + + constexpr bool process_simple_operator() + { + if (std::holds_alternative(m_Lexer.peek().classification)) + { + std::ignore = m_Lexer.next(); + } + + auto const next = m_Lexer.peek(); + if (auto const* operatorToken = std::get_if(&next.classification)) + { + std::ignore = m_Lexer.next(); + + auto const finishMultiOpOperator = [&, this](lexing::operator_or_punctuator const& expectedClosingOp) { + auto const [closingContent, classification] = m_Lexer.next(); + MIMICPP_ASSERT(lexing::token_class{expectedClosingOp} == classification, "Invalid input."); + + StringViewT 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); + } + else + { + m_TokenStack.emplace_back( + token::Identifier{ + .content = token::Identifier::OperatorInfo{.symbol = next.content}}); + } + + if (std::holds_alternative(m_Lexer.peek().classification)) + { + std::ignore = m_Lexer.next(); + } + + return true; + } + + return false; } constexpr void handle_lexer_token(StringViewT const content, lexing::operator_or_punctuator const& token) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index f36daeecf..e5d57e035 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -40,6 +40,9 @@ namespace Mock begin_function_ptr{{.name = "VisitorMock::begin_function_ptr"}}; Mock end_function_ptr{{.name = "VisitorMock::end_function_ptr"}}; + Mock begin_operator_identifier{{.name = "VisitorMock::begin_operator_identifier"}}; + Mock end_operator_identifier{{.name = "VisitorMock::end_operator_identifier"}}; + Mock add_const{{.name = "VisitorMock::add_const"}}; Mock add_volatile{{.name = "VisitorMock::add_volatile"}}; Mock add_noexcept{{.name = "VisitorMock::add_noexcept"}}; @@ -1891,3 +1894,143 @@ TEST_CASE( parser(); } } + +TEST_CASE( + "parsing::NameParser detects operators as identifiers.", + "[print][print::type]") +{ + // see: https://en.cppreference.com/w/cpp/language/operators + StringViewT const operatorSymbol = GENERATE( + "+", + "-", + "*", + "/", + "%", + "^", + "&", + "|", + "~", + "!", + "=", + "<", + ">", + "+=", + "-=", + "*=", + "/=", + "%=", + "^=", + "&=", + "|=", + "<<", + ">>", + "<<=", + ">>=", + "==", + "!=", + "<=", + ">=", + "<=>", + "&&", + "||", + "++", + "--", + ",", + "->*", + "->", + "()", + "[]"); + + StringT const spacing = GENERATE("", " "); + + VisitorMock visitor{}; + ScopedSequence sequence{}; + + SECTION("When operator + symbol form a function.") + { + StringT const input = StringT{"operator"} + spacing + StringT{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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When templated operator is given.") + { + StringT const templateSpacing = GENERATE("", " "); + StringT const input = StringT{"operator"} + spacing + StringT{operatorSymbol} + templateSpacing + "<>" + "()"; + CAPTURE(input); + + // operator<<> is no valid syntax (fortunately) + CHECKED_IF((operatorSymbol != "<" || !templateSpacing.empty())) + { + 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(); + sequence += visitor.end_template_args.expect_call(); + + sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + } + + SECTION("When operator is scope.") + { + StringT const input = "foo::" + StringT{"operator"} + spacing + StringT{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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } +} From 65448cda415dc944ea3fd1e4d6a64b5b294a177b Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 22 Apr 2025 21:54:21 +0200 Subject: [PATCH 043/130] feat: parsing::NameParser handles new, delete and co_await operators --- include/mimic++/printing/type/NameLexer.hpp | 2 + include/mimic++/printing/type/NameParser.hpp | 56 ++++++++++-- test/unit-tests/printing/TypeNameParser.cpp | 95 +++++++++++--------- 3 files changed, 103 insertions(+), 50 deletions(-) diff --git a/include/mimic++/printing/type/NameLexer.hpp b/include/mimic++/printing/type/NameLexer.hpp index 10e651a59..aa3f843cd 100644 --- a/include/mimic++/printing/type/NameLexer.hpp +++ b/include/mimic++/printing/type/NameLexer.hpp @@ -48,6 +48,7 @@ namespace mimicpp::printing::type::lexing constexpr std::array visibilityKeywords = std::to_array({"public", "protected", "private"}); constexpr std::array specKeywords = std::to_array({"const", "constexpr", "volatile", "noexcept"}); constexpr std::array contextKeywords = std::to_array({"operator", "struct", "class", "enum"}); + constexpr std::array otherKeywords = std::to_array({"new", "delete", "co_await"}); constexpr std::array digraphs = std::to_array({"and", "or", "xor", "not", "bitand", "bitor", "compl", "and_eq", "or_eq", "xor_eq", "not_eq"}); constexpr std::array braceLikes = std::to_array({"{", "}", "[", "]", "(", ")", "`", "'"}); @@ -68,6 +69,7 @@ namespace mimicpp::printing::type::lexing texts::visibilityKeywords, texts::specKeywords, texts::contextKeywords, + texts::otherKeywords, texts::digraphs); std::ranges::sort(collection); diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index da212b474..df3a158f1 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -579,6 +579,7 @@ namespace mimicpp::printing::type::parsing using Token = std::variant< token::Space, + token::OperatorKeyword, token::ArgSeparator, token::OpeningAngle, token::ClosingAngle, @@ -1306,6 +1307,9 @@ namespace mimicpp::printing::type::parsing static constexpr lexing::keyword constKeyword{"const"}; static constexpr lexing::keyword volatileKeyword{"volatile"}; static constexpr lexing::keyword noexceptKeyword{"noexcept"}; + static constexpr lexing::keyword coAwaitKeyword{"co_await"}; + static constexpr lexing::keyword newKeyword{"new"}; + static constexpr lexing::keyword deleteKeyword{"delete"}; Visitor m_Visitor; StringViewT m_Content; @@ -1393,13 +1397,18 @@ namespace mimicpp::printing::type::parsing constexpr bool process_simple_operator() { - if (std::holds_alternative(m_Lexer.peek().classification)) - { - std::ignore = m_Lexer.next(); - } + auto dropSpaceInput = [this] { + if (std::holds_alternative(m_Lexer.peek().classification)) + { + std::ignore = m_Lexer.next(); + } + }; + + dropSpaceInput(); - auto const next = m_Lexer.peek(); - if (auto const* operatorToken = std::get_if(&next.classification)) + // 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(); @@ -1430,11 +1439,42 @@ namespace mimicpp::printing::type::parsing .content = token::Identifier::OperatorInfo{.symbol = next.content}}); } - if (std::holds_alternative(m_Lexer.peek().classification)) + dropSpaceInput(); + + return true; + } + else if (auto const* keywordToken = std::get_if(&next.classification)) + { + std::ignore = m_Lexer.next(); + + StringViewT content = next.content; + + if (newKeyword == *keywordToken || deleteKeyword == *keywordToken) { - std::ignore = m_Lexer.next(); + dropSpaceInput(); + + if (auto* opAfter = std::get_if(&m_Lexer.peek().classification); + opAfter + && openingSquare == *opAfter) + { + // Strip `[]` or `[ ]` from the input. + std::ignore = m_Lexer.next(); + dropSpaceInput(); + auto const closing = m_Lexer.next(); + MIMICPP_ASSERT(closingSquare == std::get(closing.classification), "Invalid input."); + + content = StringViewT{ + next.content.data(), + closing.content.data() + closing.content.size()}; + } } + m_TokenStack.emplace_back( + token::Identifier{ + .content = token::Identifier::OperatorInfo{.symbol = content}}); + + dropSpaceInput(); + return true; } diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index e5d57e035..0766efa54 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1900,48 +1900,59 @@ TEST_CASE( "[print][print::type]") { // see: https://en.cppreference.com/w/cpp/language/operators - StringViewT const operatorSymbol = GENERATE( - "+", - "-", - "*", - "/", - "%", - "^", - "&", - "|", - "~", - "!", - "=", - "<", - ">", - "+=", - "-=", - "*=", - "/=", - "%=", - "^=", - "&=", - "|=", - "<<", - ">>", - "<<=", - ">>=", - "==", - "!=", - "<=", - ">=", - "<=>", - "&&", - "||", - "++", - "--", - ",", - "->*", - "->", - "()", - "[]"); - - StringT const spacing = GENERATE("", " "); + 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"} + })); + + StringT const spacing = requireSpacing + ? " " + : GENERATE("", " "); VisitorMock visitor{}; ScopedSequence sequence{}; From 2a6608ab24c83f0ac9f86839b3eb71bc963ff592 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 22 Apr 2025 23:57:46 +0200 Subject: [PATCH 044/130] feat: parsing::NameParser handles conversion operators --- include/mimic++/printing/type/NameParser.hpp | 72 ++++++++++++++++--- test/unit-tests/printing/TypeNameParser.cpp | 75 ++++++++++++++++++++ 2 files changed, 138 insertions(+), 9 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index df3a158f1..476f917f6 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -78,6 +78,10 @@ namespace mimicpp::printing::type::parsing { }; + class OperatorKeyword + { + }; + class ArgSeparator { public: @@ -259,7 +263,8 @@ namespace mimicpp::printing::type::parsing public: struct OperatorInfo { - StringViewT symbol{}; + using Symbol = std::variant>; + Symbol symbol{}; }; using Content = std::variant; @@ -299,12 +304,28 @@ namespace mimicpp::printing::type::parsing template static constexpr void handle_content(Visitor& visitor, OperatorInfo const& content) { - MIMICPP_ASSERT(!content.symbol.empty(), "Empty symbol is not allowed."); - visitor.begin_operator_identifier(); - visitor.add_identifier(content.symbol); + 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, StringViewT const& symbol) + { + MIMICPP_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) + { + MIMICPP_ASSERT(type, "Empty type-symbol is not allowed."); + + std::invoke(*type, visitor); + } }; class FunctionContext @@ -1170,6 +1191,25 @@ namespace mimicpp::printing::type::parsing return false; } + inline void reduce_as_conversion_operator_function_identifier(TokenStack& tokenStack) + { + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); + auto funCtx = std::get(std::move(tokenStack.back())); + tokenStack.pop_back(); + + try_reduce_as_type(tokenStack); + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); + auto targetType = std::make_shared( + std::get(std::move(tokenStack.back()))); + tokenStack.pop_back(); + + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); + tokenStack.back().emplace( + Identifier::OperatorInfo{.symbol = std::move(targetType)}); + tokenStack.emplace_back(std::move(funCtx)); + try_reduce_as_function_identifier(tokenStack); + } + inline bool try_reduce_as_function_type(TokenStack& tokenStack) { // The space is required, because the return type will always be spaced away from the parens. @@ -1189,10 +1229,17 @@ namespace mimicpp::printing::type::parsing return false; } - inline bool try_reduce_as_end(TokenStack& tokenStack) + inline bool try_reduce_as_end(TokenStack& tokenStack, bool const expectsConversionOperator) { try_reduce_as_function_context(tokenStack); + if (expectsConversionOperator) + { + reduce_as_conversion_operator_function_identifier(tokenStack); + + return try_reduce_as_function(tokenStack); + } + if (is_suffix_of(tokenStack) || try_reduce_as_function_identifier(tokenStack)) { @@ -1272,7 +1319,7 @@ namespace mimicpp::printing::type::parsing next.classification); } - try_reduce_as_end(m_TokenStack); + try_reduce_as_end(m_TokenStack, m_HasConversionOperator); if (1u == m_TokenStack.size() && std::holds_alternative(m_TokenStack.back())) @@ -1314,6 +1361,7 @@ namespace mimicpp::printing::type::parsing Visitor m_Visitor; StringViewT m_Content; lexing::NameLexer m_Lexer; + bool m_HasConversionOperator{false}; std::vector m_TokenStack{}; @@ -1389,9 +1437,13 @@ namespace mimicpp::printing::type::parsing { token::add_specs({.isNoexcept = true}, m_TokenStack); } - else if (operatorKeyword == keyword) + else if (operatorKeyword == keyword && !process_simple_operator()) { - process_simple_operator(); + // Conversion operators can not be part of a scope, thus they can not appear multiple times in a single type-name. + MIMICPP_ASSERT(!m_HasConversionOperator, "Multiple conversion operators detected."); + + m_TokenStack.emplace_back(token::OperatorKeyword{}); + m_HasConversionOperator = true; } } @@ -1443,7 +1495,9 @@ namespace mimicpp::printing::type::parsing return true; } - else if (auto const* keywordToken = std::get_if(&next.classification)) + else if (auto const* keywordToken = std::get_if(&next.classification); + keywordToken + && util::contains(std::array{newKeyword, deleteKeyword, coAwaitKeyword}, *keywordToken)) { std::ignore = m_Lexer.next(); diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 0766efa54..c48fe348e 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -2045,3 +2045,78 @@ TEST_CASE( parser(); } } + +TEST_CASE( + "parsing::NameParser detects conversion operators.", + "[print][print::type]") +{ + VisitorMock visitor{}; + ScopedSequence sequence{}; + + SECTION("When converting to simple type-name.") + { + StringT 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When converting to complex type-name.") + { + StringT 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(); + 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } +} From b98a570d7aee503f8cb896965ca4f80eeed4fa10 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Wed, 23 Apr 2025 00:09:09 +0200 Subject: [PATCH 045/130] fix: remove invalid constexpr --- include/mimic++/printing/type/NameParser.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 476f917f6..e1d11be8e 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -879,7 +879,7 @@ namespace mimicpp::printing::type::parsing return false; } - constexpr bool try_reduce_as_function_identifier(TokenStack& tokenStack) + inline bool try_reduce_as_function_identifier(TokenStack& tokenStack) { if (std::optional suffix = match_suffix(tokenStack)) { @@ -1087,7 +1087,7 @@ namespace mimicpp::printing::type::parsing } } - constexpr bool try_reduce_as_regular_type(TokenStack& tokenStack) + inline bool try_reduce_as_regular_type(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; From bba45f2a6dd43ba30bc40a2c80efca0a5936a5ce Mon Sep 17 00:00:00 2001 From: dnkpp Date: Thu, 24 Apr 2025 20:53:26 +0200 Subject: [PATCH 046/130] fix: add missing include --- include/mimic++/printing/type/NameParser.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index e1d11be8e..7abe6da87 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -11,6 +11,7 @@ #include "mimic++/Fwd.hpp" #include "mimic++/config/Config.hpp" #include "mimic++/printing/type/NameLexer.hpp" +#include "mimic++/utilities/C++23Backports.hpp" #include "mimic++/utilities/Concepts.hpp" #include "mimic++/utilities/TypeList.hpp" From a14bf73c53c5b841ce1499fc1044537a4634b26e Mon Sep 17 00:00:00 2001 From: dnkpp Date: Thu, 24 Apr 2025 21:23:35 +0200 Subject: [PATCH 047/130] fix: parsing::NameParser omits void from function args `(void)` --- include/mimic++/printing/type/NameParser.hpp | 29 ++++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 7abe6da87..224c0e14d 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -278,6 +278,15 @@ namespace mimicpp::printing::type::parsing return templateArgs.has_value(); } + [[nodiscard]] + constexpr bool is_void() const noexcept + { + auto const* id = std::get_if(&content); + + return id + && "void" == *id; + } + template constexpr void operator()(Visitor& visitor) const { @@ -473,7 +482,16 @@ namespace mimicpp::printing::type::parsing { public: using State = std::variant; - State m_State; + State state; + + [[nodiscard]] + constexpr bool is_void() const noexcept + { + auto const* const regularType = std::get_if(&state); + + return regularType + && regularType->identifier.is_void(); + } template void operator()(Visitor& visitor) const @@ -484,7 +502,7 @@ namespace mimicpp::printing::type::parsing std::visit( [&](auto const& inner) { std::invoke(inner, unwrapped); }, - m_State); + state); unwrapped.end_type(); } @@ -839,7 +857,12 @@ namespace mimicpp::printing::type::parsing FunctionArgs funArgs{}; if (args) { - funArgs.args = std::move(*args); + // We omit function args with only `void`. + if (1u != args->types.size() + || !args->types.front().is_void()) + { + funArgs.args = std::move(*args); + } } tokenStack.resize(pendingTokens.size()); From 051bcbd3a852ffe8d4bb5632dd7ed26bbbc184c7 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Thu, 24 Apr 2025 22:52:00 +0200 Subject: [PATCH 048/130] feat: printing::type::PrintVisitor sketchup --- include/mimic++/printing/TypePrinter.hpp | 3 + .../printing/type/NamePrintVisitor.hpp | 327 ++++++++++++++++++ 2 files changed, 330 insertions(+) create mode 100644 include/mimic++/printing/type/NamePrintVisitor.hpp diff --git a/include/mimic++/printing/TypePrinter.hpp b/include/mimic++/printing/TypePrinter.hpp index 764f83432..fe9c203dd 100644 --- a/include/mimic++/printing/TypePrinter.hpp +++ b/include/mimic++/printing/TypePrinter.hpp @@ -7,6 +7,9 @@ #define MIMICPP_PRINTING_TYPE_PRINTER_HPP #include "mimic++/printing/type/CommonTypes.hpp" +#include "mimic++/printing/type/NameLexer.hpp" +#include "mimic++/printing/type/NameParser.hpp" +#include "mimic++/printing/type/NamePrintVisitor.hpp" #include "mimic++/printing/type/PrintType.hpp" #include "mimic++/printing/type/Signature.hpp" #include "mimic++/printing/type/Templated.hpp" diff --git a/include/mimic++/printing/type/NamePrintVisitor.hpp b/include/mimic++/printing/type/NamePrintVisitor.hpp new file mode 100644 index 000000000..a91a30f65 --- /dev/null +++ b/include/mimic++/printing/type/NamePrintVisitor.hpp @@ -0,0 +1,327 @@ +// Copyright Dominic (DNKpp) Koepke 2024 - 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 MIMICPP_PRINTING_TYPE_NAME_PRINT_VISITOR_HPP +#define MIMICPP_PRINTING_TYPE_NAME_PRINT_VISITOR_HPP + +#pragma once + +#include "mimic++/Fwd.hpp" +#include "mimic++/config/Config.hpp" +#include "mimic++/printing/type/NameParser.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace mimicpp::printing::type +{ + [[nodiscard]] + inline auto const& alias_map() + { + static std::unordered_map const aliases{ + {"(anonymous namespace)", "{anon-ns}"}, + { "{anonymous}", "{anon-ns}"}, + {"`anonymous namespace'", "{anon-ns}"}, + { "", "lambda"} + }; + + return aliases; + } + + [[nodiscard]] + inline auto const& ignored_identifiers() + { + static std::unordered_set const collection{ + "__cxx11", + "__1"}; + + return collection; + } + + template + class PrintVisitor + { + public: + [[nodiscard]] + explicit PrintVisitor(OutIter out) noexcept(std::is_nothrow_move_constructible_v) + : m_Out{std::move(out)} + { + } + + [[nodiscard]] + constexpr OutIter out() const noexcept + { + return m_Out; + } + + constexpr void unrecognized(StringViewT const content) + { + print(content); + } + + static constexpr void begin() + { + } + + static constexpr void end() + { + } + + static constexpr void begin_type() + { + } + + static constexpr void end_type() + { + } + + constexpr void begin_scope() + { + m_Context.push_scope(); + } + + constexpr void end_scope() + { + if (!std::exchange(m_IgnoreNextScopeResolution, false)) + { + print("::"); + } + + m_Context.pop_scope(); + } + + constexpr void add_identifier(StringViewT content) + { + if (content.starts_with("{lambda(") + && content.ends_with('}')) + { + auto const closingIter = std::ranges::find(content | std::views::reverse, ')'); + print("lambda"); + print(StringViewT{closingIter.base(), content.cend() - 1}); + + return; + } + + if (ignored_identifiers().contains(content)) + { + m_IgnoreNextScopeResolution = true; + + return; + } + + auto const& aliases = alias_map(); + if (auto const iter = aliases.find(content); + iter != aliases.cend()) + { + content = iter->second; + } + print(content); + } + + constexpr void begin_template_args() + { + m_Context.push_arg_sequence(); + + print("<"); + } + + constexpr void end_template_args() + { + print(">"); + + m_Context.pop_arg_sequence(); + } + + constexpr void add_arg() + { + print(", "); + } + + static constexpr void begin_function() + { + } + + static constexpr void end_function() + { + } + + static constexpr void begin_return_type() + { + } + + constexpr void end_return_type() + { + print(" "); + } + + constexpr void begin_function_args() + { + m_Context.push_arg_sequence(); + + print("("); + } + + constexpr void end_function_args() + { + print(")"); + + m_Context.pop_arg_sequence(); + } + + constexpr void begin_function_ptr() + { + print("("); + } + + constexpr void end_function_ptr() + { + print(")"); + } + + constexpr void begin_operator_identifier() + { + print("operator"); + } + + static constexpr void end_operator_identifier() + { + } + + constexpr void add_const() + { + if (m_Context.is_spec_printable()) + { + print(" const"); + } + } + + constexpr void add_volatile() + { + if (m_Context.is_spec_printable()) + { + print(" volatile"); + } + } + + constexpr void add_noexcept() + { + if (m_Context.is_spec_printable()) + { + print(" noexcept"); + } + } + + constexpr void add_ptr() + { + if (m_Context.is_spec_printable()) + { + print("*"); + } + } + + constexpr void add_lvalue_ref() + { + if (m_Context.is_spec_printable()) + { + print("&"); + } + } + + constexpr void add_rvalue_ref() + { + if (m_Context.is_spec_printable()) + { + print("&&"); + } + } + + private: + OutIter m_Out; + bool m_IgnoreNextScopeResolution{false}; + + class Context + { + public: + [[nodiscard]] + constexpr bool is_printable() const noexcept + { + if (auto const size = m_Stack.size(); + size <= 1u) + { + return true; + } + else if (2u == size) + { + return Type::argSequence == m_Stack.front() + && Type::scope == m_Stack.back(); + } + + return false; + } + + [[nodiscard]] + constexpr bool is_spec_printable() const noexcept + { + return 0 == m_ScopeDepth; + } + + void push_scope() + { + m_Stack.emplace_back(Type::scope); + ++m_ScopeDepth; + } + + void pop_scope() + { + MIMICPP_ASSERT(0 < m_ScopeDepth, "Unbalanced depth."); + --m_ScopeDepth; + MIMICPP_ASSERT(!m_Stack.empty() && Type::scope == m_Stack.back(), "Context-stack out of sync."); + m_Stack.pop_back(); + } + + void push_arg_sequence() + { + m_Stack.emplace_back(Type::argSequence); + ++m_ArgSeqDepth; + } + + void pop_arg_sequence() + { + MIMICPP_ASSERT(0 < m_ArgSeqDepth, "Unbalanced depth."); + --m_ArgSeqDepth; + MIMICPP_ASSERT(!m_Stack.empty() && Type::argSequence == m_Stack.back(), "Context-stack out of sync."); + m_Stack.pop_back(); + } + + private: + enum class Type : std::uint8_t + { + scope, + argSequence + }; + + std::vector m_Stack{}; + int m_ScopeDepth{}; + int m_ArgSeqDepth{}; + }; + + Context m_Context{}; + + constexpr void print(StringViewT const text) + { + if (m_Context.is_printable()) + { + m_Out = std::ranges::copy(text, std::move(m_Out)).out; + } + } + }; +} + +#endif From 0c153a1e821052c3b6a17fd801d89f30abc038dd Mon Sep 17 00:00:00 2001 From: dnkpp Date: Thu, 24 Apr 2025 22:53:35 +0200 Subject: [PATCH 049/130] chore: enable NameParser as type-printer backend --- .../mimic++/printing/type/PostProcessing.hpp | 27 ++++++++++--------- .../printing/FunctionTypePostProcessing.cpp | 4 +-- .../printing/TypePostProcessing.cpp | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/include/mimic++/printing/type/PostProcessing.hpp b/include/mimic++/printing/type/PostProcessing.hpp index 822b10f39..9d1712bff 100644 --- a/include/mimic++/printing/type/PostProcessing.hpp +++ b/include/mimic++/printing/type/PostProcessing.hpp @@ -11,6 +11,8 @@ #include "mimic++/Fwd.hpp" #include "mimic++/config/Config.hpp" #include "mimic++/printing/Format.hpp" +#include "mimic++/printing/type/NameParser.hpp" +#include "mimic++/printing/type/NamePrintVisitor.hpp" #include "mimic++/utilities/Algorithm.hpp" #include @@ -574,8 +576,8 @@ namespace mimicpp::printing::type::detail rest.ends_with(token)) { name.erase( - iter.base() - std::ranges::ssize(token), - name.cend()); + iter.base() - std::ranges::ssize(token), + name.cend()); } } @@ -674,18 +676,11 @@ namespace mimicpp::printing::type::detail [[nodiscard]] inline auto& alias_map() { - static const std::unordered_map aliases{ + static std::unordered_map const aliases{ {"(anonymous namespace)", anonymousNamespaceTargetScopeText}, { "{anonymous}", anonymousNamespaceTargetScopeText}, {"`anonymous namespace'", anonymousNamespaceTargetScopeText}, - { "anonymous-namespace", anonymousNamespaceTargetScopeText}, - { "anonymous namespace", anonymousNamespaceTargetScopeText}, - { "operator-lt", "operator<"}, - { "operator-le", "operator<="}, - { "operator-gt", "operator>"}, - { "operator-ge", "operator>="}, - { "operator-spaceship", "operator<=>"}, - { "operator-invoke", "operator()"} + { "", "lambda"} }; return aliases; @@ -789,9 +784,15 @@ namespace mimicpp::printing::type template constexpr OutIter prettify_identifier(OutIter out, StringT name) { - name = detail::apply_basic_transformations(std::move(name)); + name = detail::remove_template_details(std::move(name)); - return detail::prettify(std::move(out), detail::trimmed(name)); + static_assert(parsing::parser_visitor>); + + PrintVisitor visitor{std::move(out)}; + parsing::NameParser parser{std::ref(visitor), name}; + parser(); + + return visitor.out(); } } diff --git a/test/unit-tests/printing/FunctionTypePostProcessing.cpp b/test/unit-tests/printing/FunctionTypePostProcessing.cpp index 949b71bcf..04ad23518 100644 --- a/test/unit-tests/printing/FunctionTypePostProcessing.cpp +++ b/test/unit-tests/printing/FunctionTypePostProcessing.cpp @@ -210,7 +210,7 @@ TEST_CASE( + anonTypePattern + "::" R"(operator\(\))" - R"(\(\)const)")); + R"(\(\)\s?const)")); } SECTION("When function-local anon-class is given.") @@ -240,7 +240,7 @@ TEST_CASE( + anonTypePattern + "::" R"(operator\(\))" - R"(\(\)const)")); + R"(\(\)\s?const)")); } SECTION("When function-local anon-lambda is given.") diff --git a/test/unit-tests/printing/TypePostProcessing.cpp b/test/unit-tests/printing/TypePostProcessing.cpp index bba81a53d..52ae85b96 100644 --- a/test/unit-tests/printing/TypePostProcessing.cpp +++ b/test/unit-tests/printing/TypePostProcessing.cpp @@ -989,7 +989,7 @@ TEST_CASE( + callOpScopePattern + R"(my_type\s?&)" R"(,\s?)" - R"(std::basic_string, std::allocator>\s?const\s?&&)" + R"(std::basic_string const\s?&&)" ">")); } } From 3b98c4f104f3fd0cb90b826000a5a5ea0b4be5cc Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 25 Apr 2025 22:07:11 +0200 Subject: [PATCH 050/130] test: relax several test constraints --- test/unit-tests/printing/FunctionTypePostProcessing.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit-tests/printing/FunctionTypePostProcessing.cpp b/test/unit-tests/printing/FunctionTypePostProcessing.cpp index 04ad23518..d07db4e15 100644 --- a/test/unit-tests/printing/FunctionTypePostProcessing.cpp +++ b/test/unit-tests/printing/FunctionTypePostProcessing.cpp @@ -130,7 +130,7 @@ TEST_CASE( Catch::Matchers::Matches( locReturnPattern + lambdaCallOpPattern - + R"(\(\)(const)?)")); + + R"(\(\)(\s?const)?)")); #endif } @@ -180,7 +180,7 @@ TEST_CASE( + anonNsScopePattern + "loc_fun::" + lambdaCallOpPattern - + R"(\(\)(const)?)")); + + R"(\(\)(\s?const)?)")); #endif } @@ -264,7 +264,7 @@ TEST_CASE( + anonNsScopePattern + "loc_anon_lambda_fun::" + lambdaCallOpPattern - + R"(\(\)(const)?)")); + + R"(\(\)(\s?const)?)")); #endif } From 0a9c278af139b9dbc428cd7a58ae600d5be6cde6 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 25 Apr 2025 22:07:40 +0200 Subject: [PATCH 051/130] fix: parsing::NameParser does not treat something like `(anonymous class)` as function-args --- include/mimic++/printing/type/NameParser.hpp | 24 ++++++++++++ test/unit-tests/printing/TypeNameParser.cpp | 41 ++++++++++++-------- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 224c0e14d..307c72b57 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -137,6 +137,12 @@ namespace mimicpp::printing::type::parsing StringViewT content; }; + class TypeContext + { + public: + StringViewT content; + }; + class Specs { public: @@ -629,6 +635,7 @@ namespace mimicpp::printing::type::parsing token::ClosingCurly, token::OpeningBacktick, token::ClosingSingleQuote, + token::TypeContext, token::Identifier, token::FunctionIdentifier, @@ -1154,6 +1161,12 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } + // 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, @@ -1374,6 +1387,7 @@ namespace mimicpp::printing::type::parsing static constexpr lexing::operator_or_punctuator pointer{"*"}; static constexpr lexing::operator_or_punctuator lvalueRef{"&"}; static constexpr lexing::operator_or_punctuator rvalueRef{"&&"}; + static constexpr lexing::operator_or_punctuator colon{":"}; static constexpr lexing::keyword operatorKeyword{"operator"}; static constexpr lexing::keyword constKeyword{"const"}; static constexpr lexing::keyword volatileKeyword{"volatile"}; @@ -1381,6 +1395,9 @@ namespace mimicpp::printing::type::parsing static constexpr lexing::keyword coAwaitKeyword{"co_await"}; static constexpr lexing::keyword newKeyword{"new"}; static constexpr lexing::keyword deleteKeyword{"delete"}; + static constexpr lexing::keyword classKeyword{"class"}; + static constexpr lexing::keyword structKeyword{"struct"}; + static constexpr lexing::keyword enumKeyword{"enum"}; Visitor m_Visitor; StringViewT m_Content; @@ -1469,6 +1486,13 @@ namespace mimicpp::printing::type::parsing 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}); + } } constexpr bool process_simple_operator() diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index c48fe348e..85d768068 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -86,6 +86,29 @@ namespace 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'", + }); } TEST_CASE( @@ -892,23 +915,7 @@ TEST_CASE( "parsing::NameParser detects placeholders.", "[print][print::type]") { - StringT const placeholder = GENERATE( - "{placeholder}", - "{__placeholder}", - "{place holder}", - "{place-holder}", - //"(placeholder)", this will never be supported as we can not reliably distinguish that from FunctionArgs - //"(__placeholder)", - "(place holder)", - "(place-holder)", - "", - "<__placeholder>", - "", - "", - "`placeholder'", - "`__placeholder'", - "`place holder'", - "`place-holder'"); + StringT const placeholder{GENERATE(from_range(placeholderCollection))}; VisitorMock visitor{}; ScopedSequence sequence{}; From a3f563e90dd09d8e06741c7ebf6b3cf0f6dc986c Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 25 Apr 2025 23:05:51 +0200 Subject: [PATCH 052/130] fix: parsing::NameParser correctly handles nested templates and placeholders as template-args --- include/mimic++/printing/type/NameParser.hpp | 15 +++++++ test/unit-tests/printing/TypeNameParser.cpp | 47 ++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 307c72b57..c632093a9 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -1388,6 +1388,8 @@ namespace mimicpp::printing::type::parsing static constexpr lexing::operator_or_punctuator lvalueRef{"&"}; static constexpr lexing::operator_or_punctuator rvalueRef{"&&"}; static constexpr lexing::operator_or_punctuator colon{":"}; + static constexpr lexing::operator_or_punctuator leftShift{"<<"}; + static constexpr lexing::operator_or_punctuator rightShift{">>"}; static constexpr lexing::keyword operatorKeyword{"operator"}; static constexpr lexing::keyword constKeyword{"const"}; static constexpr lexing::keyword volatileKeyword{"volatile"}; @@ -1690,6 +1692,19 @@ namespace mimicpp::printing::type::parsing 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); + } } void unwrap_msvc_like_function() diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 85d768068..a86f47e50 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -333,6 +333,28 @@ TEST_CASE( parser(); } + SECTION("When templated identifier with placeholder arg is given.") + { + StringT const placeholder{GENERATE(from_range(placeholderCollection))}; + StringT 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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + SECTION("When qualified templated identifier is given.") { StringViewT const input{"volatile foo<> const&"}; @@ -422,6 +444,31 @@ TEST_CASE( } SECTION("When templated identifier has templated arg.") + { + StringViewT 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(); + + sequence += visitor.begin_type.expect_call(); + sequence += visitor.add_identifier.expect_call("bar"); + sequence += visitor.begin_template_args.expect_call(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } + + SECTION("When templated identifier has qualified templated arg.") { StringViewT const input{"foo volatile&>"}; CAPTURE(input); From 37d09357e6da6fdbf47d7a136f7bd4b17046b4ce Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 25 Apr 2025 23:29:35 +0200 Subject: [PATCH 053/130] fix: parsing::NameParser detects placeholders wrapped in '' --- include/mimic++/printing/type/NameParser.hpp | 6 ++++-- test/unit-tests/printing/TypeNameParser.cpp | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index c632093a9..ec6f2c217 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -1421,7 +1421,7 @@ namespace mimicpp::printing::type::parsing // In fact keep all spaces directly before all opening tokens. if (auto const* nextOp = std::get_if(&m_Lexer.peek().classification); nextOp - && util::contains(std::array{openingParens, openingAngle, openingCurly, openingSquare, backtick}, *nextOp)) + && util::contains(std::array{openingParens, openingAngle, openingCurly, openingSquare, backtick, singleQuote}, *nextOp)) { token::try_reduce_as_type(m_TokenStack); m_TokenStack.emplace_back(token::Space{}); @@ -1689,7 +1689,9 @@ namespace mimicpp::printing::type::parsing m_TokenStack.emplace_back( std::in_place_type, content); - token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); + // 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. diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index a86f47e50..0001d735d 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -93,21 +93,30 @@ namespace "{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'", }); } From 380fc3213e8708183ac08f794a9012a12fba3015 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Fri, 25 Apr 2025 23:46:08 +0200 Subject: [PATCH 054/130] feat: PrintVisitor unwraps identifier in the form `'lambda\\d*'` --- include/mimic++/printing/type/NamePrintVisitor.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/mimic++/printing/type/NamePrintVisitor.hpp b/include/mimic++/printing/type/NamePrintVisitor.hpp index a91a30f65..e1bb0f73d 100644 --- a/include/mimic++/printing/type/NamePrintVisitor.hpp +++ b/include/mimic++/printing/type/NamePrintVisitor.hpp @@ -109,6 +109,16 @@ namespace mimicpp::printing::type return; } + // Lambdas can have the for `'lambda\\d*'`. Just print everything between ''. + if (constexpr StringViewT lambdaPrefix{"'lambda"}; + content.starts_with(lambdaPrefix) + && content.ends_with('\'')) + { + print(content.substr(1u, content.size() - 2u)); + + return; + } + if (ignored_identifiers().contains(content)) { m_IgnoreNextScopeResolution = true; From baa92a601f021112199dfccaec06ff3fcb5472ef Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 26 Apr 2025 12:45:44 +0200 Subject: [PATCH 055/130] fix: parsing::NameParser correctly handles `operator<<>` --- include/mimic++/printing/type/NameParser.hpp | 25 ++++++++++++++++ test/unit-tests/printing/TypeNameParser.cpp | 30 +++++++++----------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index ec6f2c217..523fbcff3 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -1534,6 +1534,31 @@ namespace mimicpp::printing::type::parsing { 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* nextOp = std::get_if(&m_Lexer.peek().classification); + 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( diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 0001d735d..80b6b9391 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -2048,28 +2048,24 @@ TEST_CASE( StringT const input = StringT{"operator"} + spacing + StringT{operatorSymbol} + templateSpacing + "<>" + "()"; CAPTURE(input); - // operator<<> is no valid syntax (fortunately) - CHECKED_IF((operatorSymbol != "<" || !templateSpacing.empty())) - { - sequence += visitor.begin.expect_call(); - sequence += visitor.begin_function.expect_call(); + 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_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(); - sequence += visitor.end_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.end_template_args.expect_call(); - sequence += visitor.begin_function_args.expect_call(); - sequence += visitor.end_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.end_function_args.expect_call(); - sequence += visitor.end_function.expect_call(); - sequence += visitor.end.expect_call(); + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); - printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); - } + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); } SECTION("When operator is scope.") From 1398ed677b09d2021fab6d249ffef9639bc43e00 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 26 Apr 2025 15:08:40 +0200 Subject: [PATCH 056/130] refactor: slightly overhaul parsing::token::Specs reference and noexcept handling --- include/mimic++/printing/type/NameParser.hpp | 80 ++++++++++++-------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 523fbcff3..a7b819b4b 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -150,26 +150,14 @@ namespace mimicpp::printing::type::parsing { bool isConst{false}; bool isVolatile{false}; - bool isNoexcept{false}; - bool isLValueRef{false}; - bool isRValueRef{false}; constexpr void merge(Layer const& others) noexcept { MIMICPP_ASSERT(!(isConst && others.isConst), "Merging same specs."); MIMICPP_ASSERT(!(isVolatile && others.isVolatile), "Merging same specs."); - MIMICPP_ASSERT(!(isNoexcept && others.isNoexcept), "Merging same specs."); - MIMICPP_ASSERT(!(isLValueRef && others.isLValueRef), "Merging same specs."); - MIMICPP_ASSERT(!(isRValueRef && others.isRValueRef), "Merging same specs."); - - MIMICPP_ASSERT(!(isLValueRef && others.isRValueRef), "Both reference types detected."); - MIMICPP_ASSERT(!(isRValueRef && others.isLValueRef), "Both reference types detected."); isConst = isConst || others.isConst; isVolatile = isVolatile || others.isVolatile; - isNoexcept = isNoexcept || others.isNoexcept; - isLValueRef = isLValueRef || others.isLValueRef; - isRValueRef = isRValueRef || others.isRValueRef; } template @@ -177,8 +165,6 @@ namespace mimicpp::printing::type::parsing { auto& inner = unwrap_visitor(visitor); - MIMICPP_ASSERT(!(isLValueRef && isRValueRef), "Both reference types detected."); - if (isConst) { inner.add_const(); @@ -188,25 +174,21 @@ namespace mimicpp::printing::type::parsing { inner.add_volatile(); } - - if (isLValueRef) - { - inner.add_lvalue_ref(); - } - else if (isRValueRef) - { - inner.add_rvalue_ref(); - } - - if (isNoexcept) - { - inner.add_noexcept(); - } } }; 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 { @@ -227,6 +209,25 @@ namespace mimicpp::printing::type::parsing 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(); + } } }; @@ -1300,6 +1301,17 @@ namespace mimicpp::printing::type::parsing return try_reduce_as_function_type(tokenStack); } + [[nodiscard]] + constexpr Specs& get_or_emplace_specs(TokenStack& tokenStack) + { + if (auto* specs = match_suffix(tokenStack)) + { + return *specs; + } + + return std::get(tokenStack.emplace_back(Specs{})); + } + constexpr void add_specs(Specs::Layer newSpecs, TokenStack& tokenStack) { if (auto* specs = match_suffix(tokenStack)) @@ -1478,7 +1490,9 @@ namespace mimicpp::printing::type::parsing } else if (noexceptKeyword == keyword) { - token::add_specs({.isNoexcept = true}, m_TokenStack); + auto& specs = token::get_or_emplace_specs(m_TokenStack); + MIMICPP_ASSERT(!specs.isNoexcept, "Specs already is a noexcept."); + specs.isNoexcept = true; } else if (operatorKeyword == keyword && !process_simple_operator()) { @@ -1629,11 +1643,15 @@ namespace mimicpp::printing::type::parsing } else if (lvalueRef == token) { - token::add_specs({.isLValueRef = true}, m_TokenStack); + auto& specs = token::get_or_emplace_specs(m_TokenStack); + MIMICPP_ASSERT(token::Specs::Refness::none == specs.refness, "Specs already is a reference."); + specs.refness = token::Specs::Refness::lvalue; } else if (rvalueRef == token) { - token::add_specs({.isRValueRef = true}, m_TokenStack); + auto& specs = token::get_or_emplace_specs(m_TokenStack); + MIMICPP_ASSERT(token::Specs::Refness::none == specs.refness, "Specs already is a reference."); + specs.refness = token::Specs::Refness::rvalue; } else if (pointer == token) { From e945c9220a63261445cf3d42a3431a93a2c0e501 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 26 Apr 2025 15:10:38 +0200 Subject: [PATCH 057/130] test: mark test as mayfail --- test/unit-tests/printing/TypePostProcessing.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit-tests/printing/TypePostProcessing.cpp b/test/unit-tests/printing/TypePostProcessing.cpp index 52ae85b96..622cba349 100644 --- a/test/unit-tests/printing/TypePostProcessing.cpp +++ b/test/unit-tests/printing/TypePostProcessing.cpp @@ -595,7 +595,7 @@ TEST_CASE( TEST_CASE( "printing::type::prettify_identifier enhances local type-names appearance.", - "[print]") + "[!mayfail][print]") { StringStreamT ss{}; @@ -671,6 +671,8 @@ TEST_CASE( SECTION("When local type is queried inside a lambda with higher arity.") { + // Todo: This case will currently fail, because parser does not handle arrays. + int d1{}; int d2[1]{}; int* ptr = &d1; From 9829e45ffd4f031f21289364882e763c5b772a42 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 26 Apr 2025 15:15:42 +0200 Subject: [PATCH 058/130] cleanup: remove unnecessary placeholder reductions --- include/mimic++/printing/type/NameParser.hpp | 33 -------------------- 1 file changed, 33 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index a7b819b4b..02967914b 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -968,31 +968,6 @@ namespace mimicpp::printing::type::parsing return true; } - constexpr bool try_reduce_as_placeholder_identifier(TokenStack& tokenStack) - { - if (is_suffix_of(tokenStack)) - { - return try_reduce_as_placeholder_identifier_wrapped(tokenStack); - } - - if (is_suffix_of(tokenStack)) - { - return try_reduce_as_placeholder_identifier_wrapped(tokenStack); - } - - if (is_suffix_of(tokenStack)) - { - return try_reduce_as_placeholder_identifier_wrapped(tokenStack); - } - - if (is_suffix_of(tokenStack)) - { - return try_reduce_as_placeholder_identifier_wrapped(tokenStack); - } - - return false; - } - inline bool try_reduce_as_function_ptr(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; @@ -1659,10 +1634,6 @@ namespace mimicpp::printing::type::parsing } else if (openingAngle == token) { - // Handle something like `{...}`. - // ^ - token::try_reduce_as_placeholder_identifier(m_TokenStack); - m_TokenStack.emplace_back( std::in_place_type, content); @@ -1680,10 +1651,6 @@ namespace mimicpp::printing::type::parsing } else if (openingParens == token) { - // Handle something like `void {...}(Args)`. - // ^ - token::try_reduce_as_placeholder_identifier(m_TokenStack); - m_TokenStack.emplace_back( std::in_place_type, content); From 4fc5270da8d05307ab5ddc6eda2f29c2972c6490 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 26 Apr 2025 15:24:39 +0200 Subject: [PATCH 059/130] refactor: extract parsing tokens into NameParserTokens.hpp --- include/mimic++/printing/type/NameParser.hpp | 636 +---------------- .../printing/type/NameParserTokens.hpp | 663 ++++++++++++++++++ 2 files changed, 664 insertions(+), 635 deletions(-) create mode 100644 include/mimic++/printing/type/NameParserTokens.hpp diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 02967914b..95cecf9bc 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -11,8 +11,8 @@ #include "mimic++/Fwd.hpp" #include "mimic++/config/Config.hpp" #include "mimic++/printing/type/NameLexer.hpp" +#include "mimic++/printing/type/NameParserTokens.hpp" #include "mimic++/utilities/C++23Backports.hpp" -#include "mimic++/utilities/Concepts.hpp" #include "mimic++/utilities/TypeList.hpp" #include @@ -21,640 +21,6 @@ namespace mimicpp::printing::type::parsing { - template - concept parser_visitor = std::movable - && requires(std::unwrap_reference_t visitor, StringViewT content) { - 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(); - 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(); - 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 token - { - class Type; - - class Space - { - }; - - class OperatorKeyword - { - }; - - class ArgSeparator - { - public: - StringViewT content; - }; - - class OpeningAngle - { - public: - StringViewT content; - }; - - class ClosingAngle - { - public: - StringViewT content; - }; - - class OpeningParens - { - public: - StringViewT content; - }; - - class ClosingParens - { - public: - StringViewT content; - }; - - class OpeningCurly - { - public: - StringViewT content; - }; - - class ClosingCurly - { - public: - StringViewT content; - }; - - class OpeningBacktick - { - public: - StringViewT content; - }; - - class ClosingSingleQuote - { - public: - StringViewT content; - }; - - class TypeContext - { - public: - StringViewT content; - }; - - class Specs - { - public: - struct Layer - { - bool isConst{false}; - bool isVolatile{false}; - - constexpr void merge(Layer const& others) noexcept - { - MIMICPP_ASSERT(!(isConst && others.isConst), "Merging same specs."); - MIMICPP_ASSERT(!(isVolatile && others.isVolatile), "Merging same specs."); - - isConst = isConst || others.isConst; - isVolatile = isVolatile || others.isVolatile; - } - - 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 - { - MIMICPP_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 FunctionArgs - { - public: - ArgSequence args{}; - - template - constexpr void operator()(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - unwrapped.begin_function_args(); - std::invoke(args, unwrapped); - unwrapped.end_function_args(); - } - }; - - class Identifier - { - public: - 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* id = std::get_if(&content); - - return id - && "void" == *id; - } - - 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, StringViewT const& content) - { - MIMICPP_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, StringViewT const& symbol) - { - MIMICPP_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) - { - MIMICPP_ASSERT(type, "Empty type-symbol is not allowed."); - - std::invoke(*type, visitor); - } - }; - - class FunctionContext - { - public: - FunctionArgs args{}; - Specs specs{}; - - template - constexpr void operator()(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - std::invoke(args, unwrapped); - std::invoke(specs, unwrapped); - } - }; - - 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 - { - MIMICPP_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); - - if (scopes) - { - std::invoke(*scopes, unwrapped); - } - - std::invoke(identifier, unwrapped); - std::invoke(specs, unwrapped); - } - }; - - 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 - { - MIMICPP_ASSERT(returnType, "Return type is mandatory for function-ptrs."); - - auto& unwrapped = unwrap_visitor(visitor); - - 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); - } - }; - - 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(); - } - - template - void operator()(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - unwrapped.begin_type(); - - std::visit( - [&](auto const& inner) { std::invoke(inner, unwrapped); }, - state); - - unwrapped.end_type(); - } - }; - - 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::invoke(*this, unwrapped); - unwrapped.end_template_args(); - } - - class FunctionType - { - public: - Type returnType{}; - FunctionContext context{}; - - template - void operator()(Visitor& visitor) const - { - 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 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(); - } - }; - - class End - { - public: - using State = std::variant; - State state{}; - - template - constexpr void operator()(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - unwrapped.begin(); - std::visit( - [&](auto const& s) { std::invoke(s, unwrapped); }, - state); - unwrapped.end(); - } - }; - } - - using Token = std::variant< - token::Space, - token::OperatorKeyword, - 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::FunctionArgs, - token::FunctionContext, - token::FunctionPtr, - token::Specs, - token::Type, - token::End>; - using TokenStack = std::vector; - - template - concept token_type = requires(Token const& token) { - { std::holds_alternative(token) } -> util::boolean_testable; - }; - namespace detail { template diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp new file mode 100644 index 000000000..9a56f042b --- /dev/null +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -0,0 +1,663 @@ +// Copyright Dominic (DNKpp) Koepke 2024 - 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 MIMICPP_PRINTING_TYPE_NAME_PARSER_TOKENS_HPP +#define MIMICPP_PRINTING_TYPE_NAME_PARSER_TOKENS_HPP + +#include "mimic++/Fwd.hpp" +#include "mimic++/config/Config.hpp" +#include "mimic++/utilities/Concepts.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mimicpp::printing::type::parsing +{ + template + concept parser_visitor = std::movable + && requires(std::unwrap_reference_t visitor, StringViewT content) { + 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(); + 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(); + 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 mimicpp::printing::type::parsing::token +{ + + class Type; + + class Space + { + }; + + class OperatorKeyword + { + }; + + class ArgSeparator + { + public: + StringViewT content; + }; + + class OpeningAngle + { + public: + StringViewT content; + }; + + class ClosingAngle + { + public: + StringViewT content; + }; + + class OpeningParens + { + public: + StringViewT content; + }; + + class ClosingParens + { + public: + StringViewT content; + }; + + class OpeningCurly + { + public: + StringViewT content; + }; + + class ClosingCurly + { + public: + StringViewT content; + }; + + class OpeningBacktick + { + public: + StringViewT content; + }; + + class ClosingSingleQuote + { + public: + StringViewT content; + }; + + class TypeContext + { + public: + StringViewT content; + }; + + class Specs + { + public: + struct Layer + { + bool isConst{false}; + bool isVolatile{false}; + + constexpr void merge(Layer const& others) noexcept + { + MIMICPP_ASSERT(!(isConst && others.isConst), "Merging same specs."); + MIMICPP_ASSERT(!(isVolatile && others.isVolatile), "Merging same specs."); + + isConst = isConst || others.isConst; + isVolatile = isVolatile || others.isVolatile; + } + + 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 + { + MIMICPP_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 FunctionArgs + { + public: + ArgSequence args{}; + + template + constexpr void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_function_args(); + std::invoke(args, unwrapped); + unwrapped.end_function_args(); + } + }; + + class Identifier + { + public: + 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* id = std::get_if(&content); + + return id + && "void" == *id; + } + + 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, StringViewT const& content) + { + MIMICPP_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, StringViewT const& symbol) + { + MIMICPP_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) + { + MIMICPP_ASSERT(type, "Empty type-symbol is not allowed."); + + std::invoke(*type, visitor); + } + }; + + class FunctionContext + { + public: + FunctionArgs args{}; + Specs specs{}; + + template + constexpr void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + std::invoke(args, unwrapped); + std::invoke(specs, unwrapped); + } + }; + + 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 + { + MIMICPP_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); + + if (scopes) + { + std::invoke(*scopes, unwrapped); + } + + std::invoke(identifier, unwrapped); + std::invoke(specs, unwrapped); + } + }; + + 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 + { + MIMICPP_ASSERT(returnType, "Return type is mandatory for function-ptrs."); + + auto& unwrapped = unwrap_visitor(visitor); + + 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); + } + }; + + 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(); + } + + template + void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin_type(); + + std::visit( + [&](auto const& inner) { std::invoke(inner, unwrapped); }, + state); + + unwrapped.end_type(); + } + }; + + 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::invoke(*this, unwrapped); + unwrapped.end_template_args(); + } + + class FunctionType + { + public: + Type returnType{}; + FunctionContext context{}; + + template + void operator()(Visitor& visitor) const + { + 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 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(); + } + }; + + class End + { + public: + using State = std::variant; + State state{}; + + template + constexpr void operator()(Visitor& visitor) const + { + auto& unwrapped = unwrap_visitor(visitor); + + unwrapped.begin(); + std::visit( + [&](auto const& s) { std::invoke(s, unwrapped); }, + state); + unwrapped.end(); + } + }; +} + +namespace mimicpp::printing::type::parsing +{ + using Token = std::variant< + token::Space, + token::OperatorKeyword, + 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::FunctionArgs, + token::FunctionContext, + token::FunctionPtr, + token::Specs, + token::Type, + token::End>; + using TokenStack = std::vector; + + template + concept token_type = requires(Token const& token) { + { std::holds_alternative(token) } -> util::boolean_testable; + }; +} + +#endif From 16b65b986c061be26c442bafbb8a7f21f288cdd0 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 26 Apr 2025 15:33:09 +0200 Subject: [PATCH 060/130] refactor: extract parsing reductions into NameParserReductions.hpp --- include/mimic++/printing/type/NameParser.hpp | 673 +---------------- .../printing/type/NameParserReductions.hpp | 687 ++++++++++++++++++ 2 files changed, 692 insertions(+), 668 deletions(-) create mode 100644 include/mimic++/printing/type/NameParserReductions.hpp diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 95cecf9bc..b00d83d55 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -11,681 +11,18 @@ #include "mimic++/Fwd.hpp" #include "mimic++/config/Config.hpp" #include "mimic++/printing/type/NameLexer.hpp" +#include "mimic++/printing/type/NameParserReductions.hpp" #include "mimic++/printing/type/NameParserTokens.hpp" #include "mimic++/utilities/C++23Backports.hpp" -#include "mimic++/utilities/TypeList.hpp" -#include +#include #include -#include +#include +#include +#include namespace mimicpp::printing::type::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 - { - MIMICPP_ASSERT(count <= tokenStack.size(), "Count exceeds stack size."); - tokenStack = tokenStack.first(tokenStack.size() - count); - } - - namespace token - { - bool try_reduce_as_type(TokenStack& tokenStack); - - constexpr void ignore_space(std::span& tokenStack) noexcept - { - if (is_suffix_of(tokenStack)) - { - remove_suffix(tokenStack, 1u); - } - } - - inline bool try_reduce_as_scope_sequence(TokenStack& tokenStack) - { - ScopeSequence::Scope scope{}; - if (auto* identifier = match_suffix(tokenStack)) - { - scope = std::move(*identifier); - } - else if (auto* funIdentifier = match_suffix(tokenStack)) - { - scope = std::move(*funIdentifier); - } - else - { - return false; - } - - tokenStack.pop_back(); - - 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) - { - if (std::optional suffix = match_suffix(tokenStack)) - { - auto& [seq, sep, type] = *suffix; - - seq.types.emplace_back(std::move(type)); - tokenStack.resize(tokenStack.size() - 2u); - - return true; - } - - if (auto* type = match_suffix(tokenStack)) - { - ArgSequence seq{}; - seq.types.emplace_back(std::move(*type)); - tokenStack.pop_back(); - 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_args(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); - - FunctionArgs funArgs{}; - if (args) - { - // We omit function args with only `void`. - if (1u != args->types.size() - || !args->types.front().is_void()) - { - funArgs.args = std::move(*args); - } - } - - tokenStack.resize(pendingTokens.size()); - tokenStack.emplace_back(std::move(funArgs)); - - return true; - } - - constexpr bool try_reduce_as_function_context(TokenStack& tokenStack) - { - std::span pendingStack{tokenStack}; - - Specs* funSpecs{}; - if (auto* specs = match_suffix(pendingStack)) - { - funSpecs = specs; - remove_suffix(pendingStack, 1u); - } - - if (auto* funArgs = match_suffix(pendingStack)) - { - remove_suffix(pendingStack, 1u); - - FunctionContext funContext{ - .args = std::move(*funArgs)}; - - if (funSpecs) - { - funContext.specs = std::move(*funSpecs); - } - - tokenStack.resize(pendingStack.size()); - tokenStack.emplace_back(std::move(funContext)); - - return true; - } - - return false; - } - - inline bool try_reduce_as_function_identifier(TokenStack& tokenStack) - { - if (std::optional suffix = match_suffix(tokenStack)) - { - auto& [identifier, funCtx] = *suffix; - FunctionIdentifier funIdentifier{ - .identifier = std::move(identifier), - .context = std::move(funCtx)}; - - tokenStack.pop_back(); - 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); - } - - template - constexpr bool try_reduce_as_placeholder_identifier_wrapped(TokenStack& tokenStack) - { - MIMICPP_ASSERT(is_suffix_of(tokenStack), "Token-stack does not have the closing token as top."); - 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(); - StringViewT const content{opening.content.data(), contentLength}; - - pendingTokens = std::span{pendingTokens.begin(), openingIter.base() - 1}; - tokenStack.resize(pendingTokens.size()); - tokenStack.emplace_back(Identifier{.content = content}); - - 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); - } - } - - auto* specs = match_suffix(pendingTokens); - if (!specs - || !specs->has_ptr()) - { - return false; - } - remove_suffix(pendingTokens, 1u); - - auto* scopeSeq = match_suffix(pendingTokens); - if (match_suffix(pendingTokens)) - { - 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) - { - if (std::optional suffix = match_suffix(tokenStack)) - { - 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(tokenStack.size() - 3); - 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); - } - } - } - - inline bool try_reduce_as_regular_type(TokenStack& tokenStack) - { - std::span pendingTokens{tokenStack}; - - Specs* suffixSpecs{}; - if (auto* specs = match_suffix(pendingTokens)) - { - suffixSpecs = specs; - remove_suffix(pendingTokens, 1u); - } - - if (!is_suffix_of(pendingTokens)) - { - return false; - } - - RegularType newType{ - .identifier = std::move(std::get(pendingTokens.back()))}; - remove_suffix(pendingTokens, 1u); - - if (suffixSpecs) - { - newType.specs = std::move(*suffixSpecs); - } - - if (auto* seq = match_suffix(pendingTokens)) - { - newType.scopes = std::move(*seq); - remove_suffix(pendingTokens, 1u); - } - - // When ScopeSequence or Identifier starts with a placeholder-like, there will be an additional space-token. - ignore_space(pendingTokens); - if (auto* prefixSpecs = match_suffix(pendingTokens)) - { - auto& layers = prefixSpecs->layers; - MIMICPP_ASSERT(1u == layers.size(), "Prefix specs can not have more than one layer."); - auto& specs = layers.front(); - MIMICPP_ASSERT(!specs.isLValueRef && !specs.isRValueRef && !specs.isNoexcept, "Invalid prefix specs."); - newType.specs.layers.front().merge(specs); - remove_suffix(pendingTokens, 1u); - } - - // 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) - { - token::try_reduce_as_function_context(tokenStack); - - return try_reduce_as_function_ptr_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); - } - - // When ScopeSequence or Identifier starts with a placeholder-like, there will be an additional space-token. - ignore_space(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()); - - // There may be something similar to a return type in front, which hasn't been reduced yet. - if (!function.returnType - && try_reduce_as_type(tokenStack)) - { - function.returnType = std::make_shared( - std::get(std::move(tokenStack.back()))); - tokenStack.pop_back(); - } - - tokenStack.emplace_back( - std::in_place_type, - std::move(function)); - - return true; - } - - return false; - } - - inline void reduce_as_conversion_operator_function_identifier(TokenStack& tokenStack) - { - MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); - auto funCtx = std::get(std::move(tokenStack.back())); - tokenStack.pop_back(); - - try_reduce_as_type(tokenStack); - MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); - auto targetType = std::make_shared( - std::get(std::move(tokenStack.back()))); - tokenStack.pop_back(); - - MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); - tokenStack.back().emplace( - Identifier::OperatorInfo{.symbol = std::move(targetType)}); - tokenStack.emplace_back(std::move(funCtx)); - try_reduce_as_function_identifier(tokenStack); - } - - inline bool try_reduce_as_function_type(TokenStack& tokenStack) - { - // The space is required, because the return type will always be spaced away from the parens. - if (std::optional suffix = match_suffix(tokenStack)) - { - auto& [returnType, space, funCtx] = *suffix; - FunctionType funType{ - .returnType = std::move(returnType), - .context = std::move(funCtx)}; - - tokenStack.resize(tokenStack.size() - 2); - tokenStack.back().emplace(std::move(funType)); - - return true; - } - - return false; - } - - inline bool try_reduce_as_end(TokenStack& tokenStack, bool const expectsConversionOperator) - { - try_reduce_as_function_context(tokenStack); - - if (expectsConversionOperator) - { - reduce_as_conversion_operator_function_identifier(tokenStack); - - return try_reduce_as_function(tokenStack); - } - - if (is_suffix_of(tokenStack) - || try_reduce_as_function_identifier(tokenStack)) - { - return try_reduce_as_function(tokenStack); - } - - if (is_suffix_of(tokenStack) - || try_reduce_as_type(tokenStack)) - { - // Do to some function-ptr reductions, there may be no actual `type`-token present. - // If not, it's probably a function-type. - if (auto* type = std::get_if(&tokenStack.back())) - { - tokenStack.back().emplace( - std::exchange(*type, {})); - - return true; - } - } - - return try_reduce_as_function_type(tokenStack); - } - - [[nodiscard]] - constexpr Specs& get_or_emplace_specs(TokenStack& tokenStack) - { - if (auto* specs = match_suffix(tokenStack)) - { - return *specs; - } - - return std::get(tokenStack.emplace_back(Specs{})); - } - - constexpr void add_specs(Specs::Layer newSpecs, TokenStack& tokenStack) - { - if (auto* specs = match_suffix(tokenStack)) - { - auto& layers = specs->layers; - MIMICPP_ASSERT(!layers.empty(), "Invalid specs state."); - layers.back().merge(newSpecs); - } - else - { - tokenStack.emplace_back( - Specs{.layers = {std::move(newSpecs)}}); - } - } - - constexpr void add_specs_layer(TokenStack& tokenStack) - { - if (auto* specs = match_suffix(tokenStack)) - { - auto& layers = specs->layers; - MIMICPP_ASSERT(!layers.empty(), "Invalid specs state."); - layers.emplace_back(); - } - else - { - tokenStack.emplace_back( - Specs{ - .layers = {2u, Specs::Layer{}} - }); - } - } - } - template class NameParser { diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp new file mode 100644 index 000000000..7e3919698 --- /dev/null +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -0,0 +1,687 @@ +// Copyright Dominic (DNKpp) Koepke 2024 - 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 MIMICPP_PRINTING_TYPE_NAME_PARSER_REDUCTIONS_HPP +#define MIMICPP_PRINTING_TYPE_NAME_PARSER_REDUCTIONS_HPP + +#include "mimic++/Fwd.hpp" +#include "mimic++/config/Config.hpp" +#include "mimic++/printing/type/NameParserTokens.hpp" +#include "mimic++/utilities/TypeList.hpp" + +#include +#include +#include +#include + +namespace mimicpp::printing::type::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 + { + MIMICPP_ASSERT(count <= tokenStack.size(), "Count exceeds stack size."); + tokenStack = tokenStack.first(tokenStack.size() - count); + } + + namespace token + { + bool try_reduce_as_type(TokenStack& tokenStack); + + constexpr void ignore_space(std::span& tokenStack) noexcept + { + if (is_suffix_of(tokenStack)) + { + remove_suffix(tokenStack, 1u); + } + } + + inline bool try_reduce_as_scope_sequence(TokenStack& tokenStack) + { + ScopeSequence::Scope scope{}; + if (auto* identifier = match_suffix(tokenStack)) + { + scope = std::move(*identifier); + } + else if (auto* funIdentifier = match_suffix(tokenStack)) + { + scope = std::move(*funIdentifier); + } + else + { + return false; + } + + tokenStack.pop_back(); + + 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) + { + if (std::optional suffix = match_suffix(tokenStack)) + { + auto& [seq, sep, type] = *suffix; + + seq.types.emplace_back(std::move(type)); + tokenStack.resize(tokenStack.size() - 2u); + + return true; + } + + if (auto* type = match_suffix(tokenStack)) + { + ArgSequence seq{}; + seq.types.emplace_back(std::move(*type)); + tokenStack.pop_back(); + 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_args(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); + + FunctionArgs funArgs{}; + if (args) + { + // We omit function args with only `void`. + if (1u != args->types.size() + || !args->types.front().is_void()) + { + funArgs.args = std::move(*args); + } + } + + tokenStack.resize(pendingTokens.size()); + tokenStack.emplace_back(std::move(funArgs)); + + return true; + } + + constexpr bool try_reduce_as_function_context(TokenStack& tokenStack) + { + std::span pendingStack{tokenStack}; + + Specs* funSpecs{}; + if (auto* specs = match_suffix(pendingStack)) + { + funSpecs = specs; + remove_suffix(pendingStack, 1u); + } + + if (auto* funArgs = match_suffix(pendingStack)) + { + remove_suffix(pendingStack, 1u); + + FunctionContext funContext{ + .args = std::move(*funArgs)}; + + if (funSpecs) + { + funContext.specs = std::move(*funSpecs); + } + + tokenStack.resize(pendingStack.size()); + tokenStack.emplace_back(std::move(funContext)); + + return true; + } + + return false; + } + + inline bool try_reduce_as_function_identifier(TokenStack& tokenStack) + { + if (std::optional suffix = match_suffix(tokenStack)) + { + auto& [identifier, funCtx] = *suffix; + FunctionIdentifier funIdentifier{ + .identifier = std::move(identifier), + .context = std::move(funCtx)}; + + tokenStack.pop_back(); + 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); + } + + template + constexpr bool try_reduce_as_placeholder_identifier_wrapped(TokenStack& tokenStack) + { + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Token-stack does not have the closing token as top."); + 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(); + StringViewT const content{opening.content.data(), contentLength}; + + pendingTokens = std::span{pendingTokens.begin(), openingIter.base() - 1}; + tokenStack.resize(pendingTokens.size()); + tokenStack.emplace_back(Identifier{.content = content}); + + 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); + } + } + + auto* specs = match_suffix(pendingTokens); + if (!specs + || !specs->has_ptr()) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + auto* scopeSeq = match_suffix(pendingTokens); + if (match_suffix(pendingTokens)) + { + 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) + { + if (std::optional suffix = match_suffix(tokenStack)) + { + 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(tokenStack.size() - 3); + 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); + } + } + } + + inline bool try_reduce_as_regular_type(TokenStack& tokenStack) + { + std::span pendingTokens{tokenStack}; + + Specs* suffixSpecs{}; + if (auto* specs = match_suffix(pendingTokens)) + { + suffixSpecs = specs; + remove_suffix(pendingTokens, 1u); + } + + if (!is_suffix_of(pendingTokens)) + { + return false; + } + + RegularType newType{ + .identifier = std::move(std::get(pendingTokens.back()))}; + remove_suffix(pendingTokens, 1u); + + if (suffixSpecs) + { + newType.specs = std::move(*suffixSpecs); + } + + if (auto* seq = match_suffix(pendingTokens)) + { + newType.scopes = std::move(*seq); + remove_suffix(pendingTokens, 1u); + } + + // When ScopeSequence or Identifier starts with a placeholder-like, there will be an additional space-token. + ignore_space(pendingTokens); + if (auto* prefixSpecs = match_suffix(pendingTokens)) + { + auto& layers = prefixSpecs->layers; + MIMICPP_ASSERT(1u == layers.size(), "Prefix specs can not have more than one layer."); + auto& specs = layers.front(); + MIMICPP_ASSERT(!specs.isLValueRef && !specs.isRValueRef && !specs.isNoexcept, "Invalid prefix specs."); + newType.specs.layers.front().merge(specs); + remove_suffix(pendingTokens, 1u); + } + + // 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) + { + token::try_reduce_as_function_context(tokenStack); + + return try_reduce_as_function_ptr_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); + } + + // When ScopeSequence or Identifier starts with a placeholder-like, there will be an additional space-token. + ignore_space(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()); + + // There may be something similar to a return type in front, which hasn't been reduced yet. + if (!function.returnType + && try_reduce_as_type(tokenStack)) + { + function.returnType = std::make_shared( + std::get(std::move(tokenStack.back()))); + tokenStack.pop_back(); + } + + tokenStack.emplace_back( + std::in_place_type, + std::move(function)); + + return true; + } + + return false; + } + + inline void reduce_as_conversion_operator_function_identifier(TokenStack& tokenStack) + { + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); + auto funCtx = std::get(std::move(tokenStack.back())); + tokenStack.pop_back(); + + try_reduce_as_type(tokenStack); + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); + auto targetType = std::make_shared( + std::get(std::move(tokenStack.back()))); + tokenStack.pop_back(); + + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); + tokenStack.back().emplace( + Identifier::OperatorInfo{.symbol = std::move(targetType)}); + tokenStack.emplace_back(std::move(funCtx)); + try_reduce_as_function_identifier(tokenStack); + } + + inline bool try_reduce_as_function_type(TokenStack& tokenStack) + { + // The space is required, because the return type will always be spaced away from the parens. + if (std::optional suffix = match_suffix(tokenStack)) + { + auto& [returnType, space, funCtx] = *suffix; + FunctionType funType{ + .returnType = std::move(returnType), + .context = std::move(funCtx)}; + + tokenStack.resize(tokenStack.size() - 2); + tokenStack.back().emplace(std::move(funType)); + + return true; + } + + return false; + } + + inline bool try_reduce_as_end(TokenStack& tokenStack, bool const expectsConversionOperator) + { + try_reduce_as_function_context(tokenStack); + + if (expectsConversionOperator) + { + reduce_as_conversion_operator_function_identifier(tokenStack); + + return try_reduce_as_function(tokenStack); + } + + if (is_suffix_of(tokenStack) + || try_reduce_as_function_identifier(tokenStack)) + { + return try_reduce_as_function(tokenStack); + } + + if (is_suffix_of(tokenStack) + || try_reduce_as_type(tokenStack)) + { + // Do to some function-ptr reductions, there may be no actual `type`-token present. + // If not, it's probably a function-type. + if (auto* type = std::get_if(&tokenStack.back())) + { + tokenStack.back().emplace( + std::exchange(*type, {})); + + return true; + } + } + + return try_reduce_as_function_type(tokenStack); + } + + [[nodiscard]] + constexpr Specs& get_or_emplace_specs(TokenStack& tokenStack) + { + if (auto* specs = match_suffix(tokenStack)) + { + return *specs; + } + + return std::get(tokenStack.emplace_back(Specs{})); + } + + constexpr void add_specs(Specs::Layer newSpecs, TokenStack& tokenStack) + { + if (auto* specs = match_suffix(tokenStack)) + { + auto& layers = specs->layers; + MIMICPP_ASSERT(!layers.empty(), "Invalid specs state."); + layers.back().merge(newSpecs); + } + else + { + tokenStack.emplace_back( + Specs{.layers = {std::move(newSpecs)}}); + } + } + + constexpr void add_specs_layer(TokenStack& tokenStack) + { + if (auto* specs = match_suffix(tokenStack)) + { + auto& layers = specs->layers; + MIMICPP_ASSERT(!layers.empty(), "Invalid specs state."); + layers.emplace_back(); + } + else + { + tokenStack.emplace_back( + Specs{ + .layers = {2u, Specs::Layer{}} + }); + } + } + } +} + +#endif From 8ce8b355fce4be5378185f661ba7e1e272856e67 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 26 Apr 2025 15:35:09 +0200 Subject: [PATCH 061/130] cleanup: remove obsolete and deactivated tests from NameLexer --- test/unit-tests/printing/TypeNameLexer.cpp | 42 ---------------------- 1 file changed, 42 deletions(-) diff --git a/test/unit-tests/printing/TypeNameLexer.cpp b/test/unit-tests/printing/TypeNameLexer.cpp index 791be7b6f..c5fcfa3fe 100644 --- a/test/unit-tests/printing/TypeNameLexer.cpp +++ b/test/unit-tests/printing/TypeNameLexer.cpp @@ -136,48 +136,6 @@ TEST_CASE( matches_end_token()); } - /*SECTION("Comma is detected.") - { - auto const expectedToken = matches_token(comma{","}); - - NameLexer lexer{","}; - CHECK_THAT( - std::as_const(lexer).peek(), - expectedToken); - - CHECK_THAT( - lexer.next(), - expectedToken); - CHECK_THAT( - std::as_const(lexer).peek(), - matches_end_token()); - - CHECK_THAT( - lexer.next(), - matches_end_token()); - } - - SECTION("Scope-resolution is detected.") - { - auto const expectedToken = matches_token(scope_resolution{"::"}); - - NameLexer lexer{"::"}; - CHECK_THAT( - std::as_const(lexer).peek(), - expectedToken); - - CHECK_THAT( - lexer.next(), - expectedToken); - CHECK_THAT( - std::as_const(lexer).peek(), - matches_end_token()); - - CHECK_THAT( - lexer.next(), - matches_end_token()); - }*/ - SECTION("Common brace-likes are detected.") { StringViewT const input = GENERATE(from_range(texts::braceLikes)); From e0b33046959b2dbd877c28386f23897f526f3e4d Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 26 Apr 2025 15:40:20 +0200 Subject: [PATCH 062/130] test: add test-case for lexing::operator_or_punctuator::text --- test/unit-tests/printing/TypeNameLexer.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/unit-tests/printing/TypeNameLexer.cpp b/test/unit-tests/printing/TypeNameLexer.cpp index c5fcfa3fe..78ab26821 100644 --- a/test/unit-tests/printing/TypeNameLexer.cpp +++ b/test/unit-tests/printing/TypeNameLexer.cpp @@ -520,3 +520,15 @@ TEST_CASE( matches_end_token()); } } + +TEST_CASE( + "lexing::operator_or_punctuator::text yields the token text.", + "[print][print::type]") +{ + StringT const tokenText{GENERATE(from_range(printing::type::lexing::operatorOrPunctuatorCollection))}; + printing::type::lexing::operator_or_punctuator const token{tokenText}; + + CHECK_THAT( + StringT{token.text()}, + Catch::Matchers::Equals(tokenText)); +} From 9e3f03bd0bff85697586e3e5f05b5f14bddb1491 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 26 Apr 2025 15:41:21 +0200 Subject: [PATCH 063/130] test: add test-case for lexing::keyword::text --- test/unit-tests/printing/TypeNameLexer.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/unit-tests/printing/TypeNameLexer.cpp b/test/unit-tests/printing/TypeNameLexer.cpp index 78ab26821..f537a669b 100644 --- a/test/unit-tests/printing/TypeNameLexer.cpp +++ b/test/unit-tests/printing/TypeNameLexer.cpp @@ -532,3 +532,15 @@ TEST_CASE( StringT{token.text()}, Catch::Matchers::Equals(tokenText)); } + +TEST_CASE( + "lexing::keyword::text yields the token text.", + "[print][print::type]") +{ + StringT const tokenText{GENERATE(from_range(printing::type::lexing::keywordCollection))}; + printing::type::lexing::keyword const token{tokenText}; + + CHECK_THAT( + StringT{token.text()}, + Catch::Matchers::Equals(tokenText)); +} From 79b26dff70e6614218de7150b85baf1599afd0f5 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 26 Apr 2025 15:45:35 +0200 Subject: [PATCH 064/130] chore: exclude compile-time only code from code-coverage --- include/mimic++/printing/type/NameLexer.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/mimic++/printing/type/NameLexer.hpp b/include/mimic++/printing/type/NameLexer.hpp index aa3f843cd..f40fdc00c 100644 --- a/include/mimic++/printing/type/NameLexer.hpp +++ b/include/mimic++/printing/type/NameLexer.hpp @@ -63,6 +63,10 @@ namespace mimicpp::printing::type::lexing constexpr std::array rest = std::to_array({"::", ";", ",", ":", "...", "?"}); } + // GCOVR_EXCL_START + + // These functions are only executed at compile-time and thus be reported as uncovered. + constexpr std::array keywordCollection = std::invoke( [] { std::array collection = util::concat_arrays( @@ -98,6 +102,8 @@ namespace mimicpp::printing::type::lexing return collection; }); + // GCOVR_EXCL_STOP + struct space { [[nodiscard]] From 3ee767c63c66bf985e8e27ee5baab5a03e79d4bc Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 26 Apr 2025 15:51:06 +0200 Subject: [PATCH 065/130] fix: correct assertion --- include/mimic++/printing/type/NameParserReductions.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 7e3919698..1cea2698f 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -493,10 +493,10 @@ namespace mimicpp::printing::type::parsing if (auto* prefixSpecs = match_suffix(pendingTokens)) { auto& layers = prefixSpecs->layers; + MIMICPP_ASSERT(token::Specs::Refness::none == prefixSpecs->refness && !prefixSpecs->isNoexcept, "Invalid prefix specs."); MIMICPP_ASSERT(1u == layers.size(), "Prefix specs can not have more than one layer."); - auto& specs = layers.front(); - MIMICPP_ASSERT(!specs.isLValueRef && !specs.isRValueRef && !specs.isNoexcept, "Invalid prefix specs."); - newType.specs.layers.front().merge(specs); + + newType.specs.layers.front().merge(layers.front()); remove_suffix(pendingTokens, 1u); } From 51b66e78d5461bba20581285582f3de9b3f6168e Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 26 Apr 2025 16:35:55 +0200 Subject: [PATCH 066/130] feat: parsing::PrintVisitor ignores namespaces in form `\d+' --- include/mimic++/printing/type/NamePrintVisitor.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/mimic++/printing/type/NamePrintVisitor.hpp b/include/mimic++/printing/type/NamePrintVisitor.hpp index e1bb0f73d..0b03ae78f 100644 --- a/include/mimic++/printing/type/NamePrintVisitor.hpp +++ b/include/mimic++/printing/type/NamePrintVisitor.hpp @@ -119,6 +119,16 @@ namespace mimicpp::printing::type return; } + // msvc injects `\d+' as auxiliar namespaces. Ignore them. + if (content.starts_with('`') + && content.ends_with('\'') + && std::ranges::all_of(content.substr(1u, content.size() - 2u), lexing::is_digit)) + { + m_IgnoreNextScopeResolution = true; + + return; + } + if (ignored_identifiers().contains(content)) { m_IgnoreNextScopeResolution = true; From a6d25bb4db3ae55c4dc09886a8b470740bde00cd Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 26 Apr 2025 17:13:24 +0200 Subject: [PATCH 067/130] feat: parsing::PrintVisitor prettifies msvc-like lambdas --- .../mimic++/printing/type/NamePrintVisitor.hpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/include/mimic++/printing/type/NamePrintVisitor.hpp b/include/mimic++/printing/type/NamePrintVisitor.hpp index 0b03ae78f..eb751c2d7 100644 --- a/include/mimic++/printing/type/NamePrintVisitor.hpp +++ b/include/mimic++/printing/type/NamePrintVisitor.hpp @@ -119,6 +119,24 @@ namespace mimicpp::printing::type return; } + // Msvc yields lambdas in form of `` + if (constexpr StringViewT lambdaPrefix{"')) + { + print("lambda"); + + auto const numberBegin = content.cbegin() + lambdaPrefix.size(); + if (auto const numberEnd = std::ranges::find_if_not(numberBegin, content.cend() - 1u, lexing::is_digit); + numberBegin != numberEnd) + { + print("#"); + print({numberBegin, numberEnd}); + } + + return; + } + // msvc injects `\d+' as auxiliar namespaces. Ignore them. if (content.starts_with('`') && content.ends_with('\'') From c08987a7c85802ec5919109d64d80f9f76167775 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 26 Apr 2025 17:14:46 +0200 Subject: [PATCH 068/130] fix: parsing::PrintVisitor unwraps general msvc-like scope-sequences --- include/mimic++/printing/type/NameParser.hpp | 5 +++++ include/mimic++/printing/type/NameParserReductions.hpp | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index b00d83d55..317410110 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -397,6 +397,11 @@ namespace mimicpp::printing::type::parsing { 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( diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 1cea2698f..9a06ef0b1 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -300,7 +300,8 @@ namespace mimicpp::printing::type::parsing || 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 From 1d3920726ca9b5b747dc341c210ccf6d01ba8013 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 26 Apr 2025 17:16:03 +0200 Subject: [PATCH 069/130] feat: parsing::PrintVisitor prettifies `anonymous-namespace' identifier --- include/mimic++/printing/type/NamePrintVisitor.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/mimic++/printing/type/NamePrintVisitor.hpp b/include/mimic++/printing/type/NamePrintVisitor.hpp index eb751c2d7..f4c8a67ee 100644 --- a/include/mimic++/printing/type/NamePrintVisitor.hpp +++ b/include/mimic++/printing/type/NamePrintVisitor.hpp @@ -29,6 +29,7 @@ namespace mimicpp::printing::type {"(anonymous namespace)", "{anon-ns}"}, { "{anonymous}", "{anon-ns}"}, {"`anonymous namespace'", "{anon-ns}"}, + {"`anonymous-namespace'", "{anon-ns}"}, { "", "lambda"} }; From 01265601aa659ad68124c368e80765237bd559b4 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 26 Apr 2025 17:20:43 +0200 Subject: [PATCH 070/130] feat: parsing::PrintVisitor fully unwraps msvc-like placeholders --- .../mimic++/printing/type/NamePrintVisitor.hpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/include/mimic++/printing/type/NamePrintVisitor.hpp b/include/mimic++/printing/type/NamePrintVisitor.hpp index f4c8a67ee..a85eab00f 100644 --- a/include/mimic++/printing/type/NamePrintVisitor.hpp +++ b/include/mimic++/printing/type/NamePrintVisitor.hpp @@ -28,8 +28,8 @@ namespace mimicpp::printing::type static std::unordered_map const aliases{ {"(anonymous namespace)", "{anon-ns}"}, { "{anonymous}", "{anon-ns}"}, - {"`anonymous namespace'", "{anon-ns}"}, - {"`anonymous-namespace'", "{anon-ns}"}, + {"anonymous namespace", "{anon-ns}"}, + {"anonymous-namespace", "{anon-ns}"}, { "", "lambda"} }; @@ -138,14 +138,18 @@ namespace mimicpp::printing::type return; } - // msvc injects `\d+' as auxiliar namespaces. Ignore them. if (content.starts_with('`') - && content.ends_with('\'') - && std::ranges::all_of(content.substr(1u, content.size() - 2u), lexing::is_digit)) + && content.ends_with('\'')) { - m_IgnoreNextScopeResolution = true; + // msvc injects `\d+' as auxiliar namespaces. Ignore them. + if (std::ranges::all_of(content.substr(1u, content.size() - 2u), lexing::is_digit)) + { + m_IgnoreNextScopeResolution = true; - return; + return; + } + + content = content.substr(1u, content.size() - 2u); } if (ignored_identifiers().contains(content)) From 15ecc1b7c91fb92211b6707dba8ebdcf5b316211 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 26 Apr 2025 17:44:04 +0200 Subject: [PATCH 071/130] fix: try_reduce_as_function_identifier ignores trailing spaces --- include/mimic++/printing/type/NameParser.hpp | 5 +++-- include/mimic++/printing/type/NameParserReductions.hpp | 9 +++++++-- test/unit-tests/printing/TypeNameParser.cpp | 9 +++++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 317410110..709233789 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -392,8 +392,9 @@ namespace mimicpp::printing::type::parsing } else if (singleQuote == token) { - if (token::try_reduce_as_function_context(m_TokenStack) - && token::try_reduce_as_function_identifier(m_TokenStack)) + token::try_reduce_as_function_context(m_TokenStack); + + if (token::try_reduce_as_function_identifier(m_TokenStack)) { unwrap_msvc_like_function(); } diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 9a06ef0b1..3abe8c28e 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -276,14 +276,19 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_function_identifier(TokenStack& tokenStack) { - if (std::optional suffix = match_suffix(tokenStack)) + std::span pendingStack{tokenStack}; + ignore_space(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.pop_back(); + tokenStack.resize(pendingStack.size() + 1u); tokenStack.back() = std::move(funIdentifier); return true; diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 80b6b9391..c9a841fdb 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1721,6 +1721,7 @@ TEST_CASE( "parsing::NameParser handles msvc-like function scopes.", "[print][print::type]") { + StringT const spacing = GENERATE("", " "); VisitorMock visitor{}; ScopedSequence sequence{}; @@ -1729,7 +1730,7 @@ TEST_CASE( SECTION("When function local type is given.") { - constexpr StringViewT input{"`void foo()'::my_type"}; + StringT const input{"`void foo()" + spacing + "'::my_type"}; CAPTURE(input); sequence += visitor.begin_scope.expect_call(); @@ -1762,8 +1763,8 @@ TEST_CASE( SECTION("Deeply nested function local type is given.") { - constexpr StringViewT input{ - "`ret2 `ret1 `ret0 inner::fn0(int const)'::`my_placeholder'::middle::fn1()const'::outer::fn2()const &'::my_type"}; + StringT 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_scope.expect_call(); @@ -1860,7 +1861,7 @@ TEST_CASE( { StringViewT const visibility = GENERATE("public", "private", "protected"); StringViewT const typeClass = GENERATE("struct", "class", "enum"); - StringT const input = StringT{typeClass} + " `" + StringT{visibility} + ": void __cdecl foo() __ptr64'::my_type"; + StringT const input = StringT{typeClass} + " `" + StringT{visibility} + ": void __cdecl foo() __ptr64" + spacing + "'::my_type"; CAPTURE(input); sequence += visitor.begin_scope.expect_call(); From dcf0498025b46c2a65bb93f152d185350228e444 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 26 Apr 2025 18:04:59 +0200 Subject: [PATCH 072/130] fix: parsing::NameParser does not treat ::() as function-args --- include/mimic++/printing/type/NameParser.hpp | 8 +++++-- .../printing/type/NameParserReductions.hpp | 21 ++++++++++++++++--- .../printing/type/NameParserTokens.hpp | 7 +++++++ test/unit-tests/printing/TypeNameParser.cpp | 16 -------------- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 709233789..5a9fead3c 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -308,6 +308,10 @@ namespace mimicpp::printing::type::parsing { token::try_reduce_as_function_context(m_TokenStack) && 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) @@ -442,8 +446,6 @@ namespace mimicpp::printing::type::parsing m_TokenStack.pop_back(); } - scopes.scopes.emplace_back(std::move(funIdentifier)); - if (is_suffix_of(m_TokenStack)) { m_TokenStack.pop_back(); @@ -471,6 +473,8 @@ namespace mimicpp::printing::type::parsing { m_TokenStack.emplace_back(std::move(scopes)); } + + m_TokenStack.emplace_back(std::move(funIdentifier)); } }; } diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 3abe8c28e..74f0e80bf 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -108,12 +108,19 @@ namespace mimicpp::printing::type::parsing 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(tokenStack)) + if (auto* identifier = match_suffix(pendingTokens)) { scope = std::move(*identifier); } - else if (auto* funIdentifier = match_suffix(tokenStack)) + else if (auto* funIdentifier = match_suffix(pendingTokens)) { scope = std::move(*funIdentifier); } @@ -122,7 +129,8 @@ namespace mimicpp::printing::type::parsing return false; } - tokenStack.pop_back(); + remove_suffix(pendingTokens, 1u); + tokenStack.resize(pendingTokens.size()); if (auto* sequence = match_suffix(tokenStack)) { @@ -225,6 +233,13 @@ namespace mimicpp::printing::type::parsing } 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; + } + FunctionArgs funArgs{}; if (args) { diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp index 9a56f042b..33205b0e0 100644 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -85,6 +85,12 @@ namespace mimicpp::printing::type::parsing::token { }; + class ScopeResolution + { + public: + StringViewT content; + }; + class ArgSeparator { public: @@ -631,6 +637,7 @@ namespace mimicpp::printing::type::parsing using Token = std::variant< token::Space, token::OperatorKeyword, + token::ScopeResolution, token::ArgSeparator, token::OpeningAngle, token::ClosingAngle, diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index c9a841fdb..417603af9 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -187,22 +187,6 @@ TEST_CASE( printing::type::parsing::NameParser parser{std::ref(visitor), input}; parser(); } - - SECTION("When root scope is explicitly given.") - { - StringViewT const scope = GENERATE(from_range(identifiers)); - StringT const input = "::" + StringT{scope}; - CAPTURE(input); - - sequence += visitor.begin_type.expect_call(); - sequence += visitor.add_identifier.expect_call(scope); - sequence += visitor.end_type.expect_call(); - - sequence += visitor.end.expect_call(); - - printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); - } } TEST_CASE( From 0502f027574b8f68923a7b61d7b29e61dafd1572 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 26 Apr 2025 18:57:48 +0200 Subject: [PATCH 073/130] fix: try_reduce_as_function_ptr ignores optional space --- include/mimic++/printing/type/NameParserReductions.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 74f0e80bf..ec779c2b4 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -388,6 +388,8 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } + ignore_space(pendingTokens); + if (!is_suffix_of(pendingTokens)) { return false; From d6b66fda8abdd687654d176f0fdca82196c8d3c1 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sun, 27 Apr 2025 15:40:54 +0200 Subject: [PATCH 074/130] fix: parsing::NameParser correctly handles function-types as template-argument --- .../printing/type/NameParserReductions.hpp | 56 ++++++++-------- .../printing/type/NameParserTokens.hpp | 65 ++++++++++--------- test/unit-tests/printing/TypeNameParser.cpp | 43 +++++++++++- 3 files changed, 105 insertions(+), 59 deletions(-) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index ec779c2b4..dc9c270ff 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -421,6 +421,25 @@ namespace mimicpp::printing::type::parsing return true; } + inline bool try_reduce_as_function_type(TokenStack& tokenStack) + { + // The space is required, because the return type will always be spaced away from the parens. + if (std::optional suffix = match_suffix(tokenStack)) + { + auto& [returnType, space, funCtx] = *suffix; + FunctionType funType{ + .returnType = std::make_shared(std::move(returnType)), + .context = std::move(funCtx)}; + + tokenStack.resize(tokenStack.size() - 2u); + tokenStack.back().emplace(std::move(funType)); + + return true; + } + + return false; + } + namespace detail { void handled_nested_function_ptr(TokenStack& tokenStack, FunctionPtr::NestedInfo info); @@ -477,6 +496,10 @@ namespace mimicpp::printing::type::parsing { try_reduce_as_function_ptr_type(tokenStack); } + else + { + try_reduce_as_function_type(tokenStack); + } } } @@ -542,6 +565,7 @@ namespace mimicpp::printing::type::parsing token::try_reduce_as_function_context(tokenStack); return try_reduce_as_function_ptr_type(tokenStack) + || try_reduce_as_function_type(tokenStack) || try_reduce_as_regular_type(tokenStack); } @@ -609,25 +633,6 @@ namespace mimicpp::printing::type::parsing try_reduce_as_function_identifier(tokenStack); } - inline bool try_reduce_as_function_type(TokenStack& tokenStack) - { - // The space is required, because the return type will always be spaced away from the parens. - if (std::optional suffix = match_suffix(tokenStack)) - { - auto& [returnType, space, funCtx] = *suffix; - FunctionType funType{ - .returnType = std::move(returnType), - .context = std::move(funCtx)}; - - tokenStack.resize(tokenStack.size() - 2); - tokenStack.back().emplace(std::move(funType)); - - return true; - } - - return false; - } - inline bool try_reduce_as_end(TokenStack& tokenStack, bool const expectsConversionOperator) { try_reduce_as_function_context(tokenStack); @@ -648,18 +653,13 @@ namespace mimicpp::printing::type::parsing if (is_suffix_of(tokenStack) || try_reduce_as_type(tokenStack)) { - // Do to some function-ptr reductions, there may be no actual `type`-token present. - // If not, it's probably a function-type. - if (auto* type = std::get_if(&tokenStack.back())) - { - tokenStack.back().emplace( - std::exchange(*type, {})); + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state."); - return true; - } + tokenStack.back().emplace( + std::exchange(std::get(tokenStack.back()), {})); } - return try_reduce_as_function_type(tokenStack); + return false; } [[nodiscard]] diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp index 33205b0e0..60704497b 100644 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -74,7 +74,6 @@ namespace mimicpp::printing::type::parsing namespace mimicpp::printing::type::parsing::token { - class Type; class Space @@ -436,6 +435,8 @@ namespace mimicpp::printing::type::parsing::token { auto& unwrapped = unwrap_visitor(visitor); + unwrapped.begin_type(); + if (scopes) { std::invoke(*scopes, unwrapped); @@ -443,6 +444,33 @@ namespace mimicpp::printing::type::parsing::token 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 + { + MIMICPP_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(); } }; @@ -476,6 +504,8 @@ namespace mimicpp::printing::type::parsing::token auto& unwrapped = unwrap_visitor(visitor); + unwrapped.begin_type(); + unwrapped.begin_return_type(); std::invoke(*returnType, visitor); unwrapped.end_return_type(); @@ -490,13 +520,15 @@ namespace mimicpp::printing::type::parsing::token unwrapped.end_function_ptr(); std::invoke(context, unwrapped); + + unwrapped.end_type(); } }; class Type { public: - using State = std::variant; + using State = std::variant; State state; [[nodiscard]] @@ -513,13 +545,9 @@ namespace mimicpp::printing::type::parsing::token { auto& unwrapped = unwrap_visitor(visitor); - unwrapped.begin_type(); - std::visit( [&](auto const& inner) { std::invoke(inner, unwrapped); }, state); - - unwrapped.end_type(); } }; @@ -557,29 +585,6 @@ namespace mimicpp::printing::type::parsing::token unwrapped.end_template_args(); } - class FunctionType - { - public: - Type returnType{}; - FunctionContext context{}; - - template - void operator()(Visitor& visitor) const - { - 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 Function { public: @@ -615,7 +620,7 @@ namespace mimicpp::printing::type::parsing::token class End { public: - using State = std::variant; + using State = std::variant; State state{}; template diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 417603af9..2b6d1fb9e 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -893,13 +893,14 @@ TEST_CASE( StringT const suffix = specSpace + spec; sequence += visitor.begin.expect_call(); - sequence += visitor.begin_function.expect_call(); SECTION("When function has an unqualified return-type.") { StringT 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"); @@ -926,6 +927,8 @@ TEST_CASE( StringT 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"); @@ -949,6 +952,44 @@ TEST_CASE( printing::type::parsing::NameParser parser{std::ref(visitor), input}; parser(); } + + SECTION("When function is template argument.") + { + StringT 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(); + { + 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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser(); + } } TEST_CASE( From 404bdd0986234f164bdcba16704faeca68e18768 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 28 Apr 2025 11:06:21 +0200 Subject: [PATCH 075/130] chore: split operator() of parsing::NameParser into two separate functions: parse_type and parse_function --- include/mimic++/printing/type/NameParser.hpp | 80 +++++++++-- .../printing/type/NameParserReductions.hpp | 33 +---- .../printing/type/NameParserTokens.hpp | 21 +-- test/unit-tests/printing/TypeNameParser.cpp | 126 ++++++++++-------- 4 files changed, 135 insertions(+), 125 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 5a9fead3c..380176fdd 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -35,29 +35,41 @@ namespace mimicpp::printing::type::parsing { } - constexpr void operator()() + constexpr void parse_type() { - for (lexing::token next = m_Lexer.next(); - !std::holds_alternative(next.classification); - next = m_Lexer.next()) + parse(); + token::try_reduce_as_type(m_TokenStack); + if (!finalize()) { - std::visit( - [&](auto const& tokenClass) { handle_lexer_token(next.content, tokenClass); }, - next.classification); + emit_unrecognized(); } + } - try_reduce_as_end(m_TokenStack, m_HasConversionOperator); + constexpr void parse_function() + { + parse(); + token::try_reduce_as_function_context(m_TokenStack); - if (1u == m_TokenStack.size() - && std::holds_alternative(m_TokenStack.back())) + if (m_HasConversionOperator) { - std::invoke( - std::get(m_TokenStack.back()), - m_Visitor); + token::reduce_as_conversion_operator_function_identifier(m_TokenStack); } else { - unwrap_visitor(m_Visitor).unrecognized(m_Content); + is_suffix_of(m_TokenStack) + || token::try_reduce_as_function_identifier(m_TokenStack); + } + + token::try_reduce_as_function(m_TokenStack); + if (!finalize()) + { + // 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 (!finalize()) + { + emit_unrecognized(); + } } } @@ -98,6 +110,46 @@ namespace mimicpp::printing::type::parsing std::vector m_TokenStack{}; + constexpr void 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); + } + } + + template + constexpr bool finalize() + { + auto& unwrapped = unwrap_visitor(m_Visitor); + + if (1u == m_TokenStack.size()) + { + if (auto const* const end = std::get_if(&m_TokenStack.back())) + { + unwrapped.begin(); + std::invoke( + std::get(m_TokenStack.back()), + m_Visitor); + unwrapped.end(); + + return true; + } + } + + return false; + } + + constexpr void emit_unrecognized() + { + auto& unwrapped = unwrap_visitor(m_Visitor); + unwrapped.unrecognized(m_Content); + } + static constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, [[maybe_unused]] lexing::end const& end) { util::unreachable(); diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index dc9c270ff..57bc7fdc7 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -604,9 +604,7 @@ namespace mimicpp::printing::type::parsing tokenStack.pop_back(); } - tokenStack.emplace_back( - std::in_place_type, - std::move(function)); + tokenStack.emplace_back(std::move(function)); return true; } @@ -633,35 +631,6 @@ namespace mimicpp::printing::type::parsing try_reduce_as_function_identifier(tokenStack); } - inline bool try_reduce_as_end(TokenStack& tokenStack, bool const expectsConversionOperator) - { - try_reduce_as_function_context(tokenStack); - - if (expectsConversionOperator) - { - reduce_as_conversion_operator_function_identifier(tokenStack); - - return try_reduce_as_function(tokenStack); - } - - if (is_suffix_of(tokenStack) - || try_reduce_as_function_identifier(tokenStack)) - { - return try_reduce_as_function(tokenStack); - } - - if (is_suffix_of(tokenStack) - || try_reduce_as_type(tokenStack)) - { - MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state."); - - tokenStack.back().emplace( - std::exchange(std::get(tokenStack.back()), {})); - } - - return false; - } - [[nodiscard]] constexpr Specs& get_or_emplace_specs(TokenStack& tokenStack) { diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp index 60704497b..b77734860 100644 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -616,25 +616,6 @@ namespace mimicpp::printing::type::parsing::token unwrapped.end_function(); } }; - - class End - { - public: - using State = std::variant; - State state{}; - - template - constexpr void operator()(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - unwrapped.begin(); - std::visit( - [&](auto const& s) { std::invoke(s, unwrapped); }, - state); - unwrapped.end(); - } - }; } namespace mimicpp::printing::type::parsing @@ -663,7 +644,7 @@ namespace mimicpp::printing::type::parsing token::FunctionPtr, token::Specs, token::Type, - token::End>; + token::Function>; using TokenStack = std::vector; template diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 2b6d1fb9e..58c523aa6 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -131,7 +131,15 @@ TEST_CASE( SCOPED_EXP visitor.unrecognized.expect_call("Hello, World!"); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + SECTION("When parsing type.") + { + parser.parse_type(); + } + + SECTION("When parsing function.") + { + parser.parse_function(); + } } TEST_CASE( @@ -157,7 +165,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When multiple scopes are given.") @@ -185,7 +193,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } } @@ -213,7 +221,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When given before the actual identifier with ref/pointer.") @@ -233,7 +241,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When given after the actual identifier.") @@ -251,7 +259,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When given after the actual identifier with ref/pointer.") @@ -271,7 +279,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("Coming all together") @@ -295,7 +303,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } } @@ -323,7 +331,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When templated identifier with placeholder arg is given.") @@ -345,7 +353,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When qualified templated identifier is given.") @@ -367,7 +375,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When templated identifier with multiple args is given.") @@ -398,7 +406,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When templated identifier with multiple args with specs is given.") @@ -433,7 +441,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When templated identifier has templated arg.") @@ -458,7 +466,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When templated identifier has qualified templated arg.") @@ -496,7 +504,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When template is part of a scope.") @@ -518,7 +526,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } } @@ -566,7 +574,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("When templated function identifier is given.") @@ -605,7 +613,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("When function identifier with single arg is given.") @@ -647,7 +655,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("When function identifier with templated arg is given.") @@ -695,7 +703,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("When function identifier with multiple args is given.") @@ -741,7 +749,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("When function is a scope.") @@ -803,7 +811,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("When function identifier with qualified return type is given.") @@ -841,7 +849,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("When function identifier with templated return type is given.") @@ -877,7 +885,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } } @@ -919,7 +927,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When function has a qualified return-type.") @@ -950,7 +958,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When function is template argument.") @@ -988,7 +996,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } } @@ -1016,7 +1024,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("Templated placeholders are detected.") @@ -1035,7 +1043,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("Qualified placeholders are detected.") @@ -1054,7 +1062,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("Scoped placeholders are detected.") @@ -1078,7 +1086,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("Placeholder return types are detected.") @@ -1102,7 +1110,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("Functions with placeholder names are detected.") @@ -1131,7 +1139,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("Functions with placeholder scoped names are detected.") @@ -1164,7 +1172,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } } @@ -1211,7 +1219,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When noexcept function pointer without arguments is given.") @@ -1240,7 +1248,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When function pointer-ref without arguments is given.") @@ -1270,7 +1278,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When function pointer with arguments is given.") @@ -1313,7 +1321,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When member function pointer without arguments is given.") @@ -1346,7 +1354,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When member function pointer with qualifications is given.") @@ -1382,7 +1390,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When return-type is a function pointer.") @@ -1426,7 +1434,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When Function-Ptr with a function-ptr return-type is given.") @@ -1474,7 +1482,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When function-ptr with function-ptr parameter is given.") @@ -1520,7 +1528,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When parameter is a template argument.") @@ -1558,7 +1566,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } } @@ -1615,7 +1623,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When qualified function local type is given.") @@ -1644,7 +1652,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When nested function local type is given.") @@ -1674,7 +1682,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When deeply nested function local type is given.") @@ -1738,7 +1746,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } } @@ -1783,7 +1791,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("Deeply nested function local type is given.") @@ -1879,7 +1887,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } SECTION("When decorated function local type is given.") @@ -1914,7 +1922,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } } @@ -1942,7 +1950,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), identifier}; - parser(); + parser.parse_type(); } SECTION("As function name.") @@ -1961,7 +1969,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("As template.") @@ -1980,7 +1988,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } } @@ -2065,7 +2073,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("When templated operator is given.") @@ -2091,7 +2099,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("When operator is scope.") @@ -2127,7 +2135,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_type(); } } @@ -2159,7 +2167,7 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } SECTION("When converting to complex type-name.") @@ -2202,6 +2210,6 @@ TEST_CASE( sequence += visitor.end.expect_call(); printing::type::parsing::NameParser parser{std::ref(visitor), input}; - parser(); + parser.parse_function(); } } From af767c677dc55123f4aecd13b98fe8a5dc08b991 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 28 Apr 2025 11:14:59 +0200 Subject: [PATCH 076/130] chore: split prettify_identifier into separate functions: prettify_type and prettify_function --- include/mimic++/Stacktrace.hpp | 2 +- .../mimic++/printing/state/CommonTypes.hpp | 2 +- .../mimic++/printing/type/PostProcessing.hpp | 18 ++- include/mimic++/printing/type/PrintType.hpp | 2 +- include/mimic++/printing/type/Templated.hpp | 2 +- .../printing/FunctionTypePostProcessing.cpp | 36 +++--- .../printing/TypePostProcessing.cpp | 106 +++++++++--------- 7 files changed, 90 insertions(+), 78 deletions(-) diff --git a/include/mimic++/Stacktrace.hpp b/include/mimic++/Stacktrace.hpp index cb54af4ca..e41d48668 100644 --- a/include/mimic++/Stacktrace.hpp +++ b/include/mimic++/Stacktrace.hpp @@ -451,7 +451,7 @@ namespace mimicpp::stacktrace::detail std::move(out), "#L{}, `", stacktrace.source_line(index)); - out = printing::type::prettify_identifier(std::move(out), stacktrace.description(index)); + out = printing::type::prettify_function(std::move(out), stacktrace.description(index)); out = format::format_to(std::move(out), "`"); return out; diff --git a/include/mimic++/printing/state/CommonTypes.hpp b/include/mimic++/printing/state/CommonTypes.hpp index acd3c408e..0ef7f933a 100644 --- a/include/mimic++/printing/state/CommonTypes.hpp +++ b/include/mimic++/printing/state/CommonTypes.hpp @@ -45,7 +45,7 @@ namespace mimicpp::printing::detail::state std::move(out), "#L{}, `", loc.line()); - out = type::prettify_identifier(std::move(out), loc.function_name()); + out = type::prettify_function(std::move(out), loc.function_name()); out = format::format_to(std::move(out), "`"); return out; diff --git a/include/mimic++/printing/type/PostProcessing.hpp b/include/mimic++/printing/type/PostProcessing.hpp index 9d1712bff..2d3df2a00 100644 --- a/include/mimic++/printing/type/PostProcessing.hpp +++ b/include/mimic++/printing/type/PostProcessing.hpp @@ -43,7 +43,7 @@ namespace mimicpp::printing::type * this function simply outputs the provided name without any modifications. */ template - constexpr OutIter prettify_identifier(OutIter out, StringT name); + constexpr OutIter prettify_type(OutIter out, StringT name); } #ifndef MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES @@ -782,7 +782,19 @@ namespace mimicpp::printing::type::detail namespace mimicpp::printing::type { template - constexpr OutIter prettify_identifier(OutIter out, StringT name) + constexpr OutIter prettify_type(OutIter out, StringT name) + { + static_assert(parsing::parser_visitor>); + + PrintVisitor visitor{std::move(out)}; + parsing::NameParser parser{std::ref(visitor), name}; + parser.parse_type(); + + return visitor.out(); + } + + template + constexpr OutIter prettify_function(OutIter out, StringT name) { name = detail::remove_template_details(std::move(name)); @@ -790,7 +802,7 @@ namespace mimicpp::printing::type PrintVisitor visitor{std::move(out)}; parsing::NameParser parser{std::ref(visitor), name}; - parser(); + parser.parse_function(); return visitor.out(); } diff --git a/include/mimic++/printing/type/PrintType.hpp b/include/mimic++/printing/type/PrintType.hpp index 255ce5037..df3d287c7 100644 --- a/include/mimic++/printing/type/PrintType.hpp +++ b/include/mimic++/printing/type/PrintType.hpp @@ -116,7 +116,7 @@ namespace mimicpp::printing::type::detail template OutIter print_type_to([[maybe_unused]] util::priority_tag<0u> const, OutIter out) { - return type::prettify_identifier( + return type::prettify_type( std::move(out), type::type_name()); } diff --git a/include/mimic++/printing/type/Templated.hpp b/include/mimic++/printing/type/Templated.hpp index 64aa386a4..89e93b5a0 100644 --- a/include/mimic++/printing/type/Templated.hpp +++ b/include/mimic++/printing/type/Templated.hpp @@ -35,7 +35,7 @@ namespace mimicpp::printing::type::detail MIMICPP_ASSERT(iter != name.cend(), "Given name is not a template."); name.erase(iter, name.end()); - return type::prettify_identifier(std::move(out), std::move(name)); + return type::prettify_type(std::move(out), std::move(name)); } template diff --git a/test/unit-tests/printing/FunctionTypePostProcessing.cpp b/test/unit-tests/printing/FunctionTypePostProcessing.cpp index d07db4e15..48d57464a 100644 --- a/test/unit-tests/printing/FunctionTypePostProcessing.cpp +++ b/test/unit-tests/printing/FunctionTypePostProcessing.cpp @@ -92,7 +92,7 @@ namespace } TEST_CASE( - "printing::type::prettify_identifier enhances std::source_location::function_name appearance.", + "printing::type::prettify_function enhances std::source_location::function_name appearance.", "[print]") { StringStreamT ss{}; @@ -102,7 +102,7 @@ TEST_CASE( constexpr auto loc = std::source_location::current(); CAPTURE(loc.function_name()); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, loc.function_name()); @@ -116,7 +116,7 @@ TEST_CASE( constexpr auto loc = type_post_processing_lambda_loc(); CAPTURE(loc.function_name()); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, loc.function_name()); @@ -139,7 +139,7 @@ TEST_CASE( constexpr auto loc = type_post_processing_nested_lambda_loc(); CAPTURE(loc.function_name()); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, loc.function_name()); @@ -164,7 +164,7 @@ TEST_CASE( constexpr auto loc = loc_fun(); CAPTURE(loc.function_name()); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, loc.function_name()); @@ -197,7 +197,7 @@ TEST_CASE( constexpr auto loc = obj(); CAPTURE(loc.function_name()); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, loc.function_name()); @@ -227,7 +227,7 @@ TEST_CASE( constexpr auto loc = obj(); CAPTURE(loc.function_name()); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, loc.function_name()); @@ -248,7 +248,7 @@ TEST_CASE( constexpr auto loc = loc_anon_lambda_fun(); CAPTURE(loc.function_name()); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, loc.function_name()); @@ -274,7 +274,7 @@ TEST_CASE( auto const loc = my_template{}.foo({}); CAPTURE(loc.function_name()); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, loc.function_name()); @@ -339,7 +339,7 @@ namespace } TEST_CASE( - "printing::type::prettify_identifier enhances Stacktrace::description appearance.", + "printing::type::prettify_function enhances Stacktrace::description appearance.", "[print]") { StringStreamT ss{}; @@ -351,7 +351,7 @@ TEST_CASE( StringT const name = trace.description(0u); CAPTURE(name); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, name); @@ -367,7 +367,7 @@ TEST_CASE( StringT const name = trace.description(0u); CAPTURE(name); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, name); @@ -391,7 +391,7 @@ TEST_CASE( StringT const name = trace.description(0u); CAPTURE(name); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, name); @@ -421,7 +421,7 @@ TEST_CASE( StringT const name = trace.description(0u); CAPTURE(name); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, name); @@ -456,7 +456,7 @@ TEST_CASE( StringT const name = trace.description(0u); CAPTURE(name); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, name); @@ -493,7 +493,7 @@ TEST_CASE( StringT const name = trace.description(0u); CAPTURE(name); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, name); @@ -520,7 +520,7 @@ TEST_CASE( StringT const name = trace.description(0u); CAPTURE(name); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, name); @@ -547,7 +547,7 @@ TEST_CASE( StringT const name = trace.description(0u); CAPTURE(name); - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, name); diff --git a/test/unit-tests/printing/TypePostProcessing.cpp b/test/unit-tests/printing/TypePostProcessing.cpp index 622cba349..07433ff52 100644 --- a/test/unit-tests/printing/TypePostProcessing.cpp +++ b/test/unit-tests/printing/TypePostProcessing.cpp @@ -197,7 +197,7 @@ namespace } TEST_CASE( - "printing::type::prettify_identifier enhances names appearance.", + "printing::type::prettify_type enhances names appearance.", "[print]") { StringStreamT ss{}; @@ -207,7 +207,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -224,7 +224,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -241,7 +241,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -258,7 +258,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -271,7 +271,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -284,7 +284,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -302,7 +302,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -315,7 +315,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -333,7 +333,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -352,7 +352,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -370,7 +370,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -390,7 +390,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -410,7 +410,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -426,7 +426,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -444,7 +444,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -461,7 +461,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -478,7 +478,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -495,7 +495,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name().my_typeLvalueFunction())>(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -512,7 +512,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name().my_typeConstLvalueFunction())>(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -529,7 +529,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -546,7 +546,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name().my_typeConstRvalueFunction())>(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -563,7 +563,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -580,7 +580,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -594,7 +594,7 @@ TEST_CASE( } TEST_CASE( - "printing::type::prettify_identifier enhances local type-names appearance.", + "printing::type::prettify_type enhances local type-names appearance.", "[!mayfail][print]") { StringStreamT ss{}; @@ -608,7 +608,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -627,7 +627,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -653,7 +653,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{_ss}, rawName); REQUIRE_THAT( @@ -689,7 +689,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{*_ss}, rawName); REQUIRE_THAT( @@ -723,7 +723,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{*_ss}, rawName); REQUIRE_THAT( @@ -743,7 +743,7 @@ TEST_CASE( } TEST_CASE( - "printing::type::prettify_identifier type-names enhances function type-names appearance.", + "printing::type::prettify_type type-names enhances function type-names appearance.", "[print]") { StringStreamT ss{}; @@ -754,7 +754,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); @@ -775,7 +775,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); @@ -793,7 +793,7 @@ TEST_CASE( } TEST_CASE( - "printing::type::prettify_identifier type-names enhances function-pointer type-names appearance.", + "printing::type::prettify_type type-names enhances function-pointer type-names appearance.", "[print]") { StringStreamT ss{}; @@ -804,7 +804,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); @@ -825,7 +825,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); @@ -873,7 +873,7 @@ namespace } TEST_CASE( - "printing::type::prettify_identifier enhances template type-names appearance.", + "printing::type::prettify_type enhances template type-names appearance.", "[print]") { StringStreamT ss{}; @@ -883,7 +883,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name>(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -896,7 +896,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name::my_type>(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -909,7 +909,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name>::foo)>(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); @@ -963,7 +963,7 @@ TEST_CASE( + argListPattern + R"(\))"; - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -977,7 +977,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name>(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -1140,7 +1140,7 @@ namespace } TEST_CASE( - "printing::type::prettify_identifier supports operator<, <=, >, >= and <=>.", + "printing::type::prettify_type supports operator<, <=, >, >= and <=>.", "[print]") { StringStreamT ss{}; @@ -1156,7 +1156,7 @@ TEST_CASE( })); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -1179,7 +1179,7 @@ TEST_CASE( })); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -1198,7 +1198,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(42))>(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -1212,7 +1212,7 @@ TEST_CASE( } TEST_CASE( - "printing::type::prettify_identifier supports operator().", + "printing::type::prettify_type supports operator().", "[print]") { StringStreamT ss{}; @@ -1222,7 +1222,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); REQUIRE_THAT( @@ -1239,7 +1239,7 @@ TEST_CASE( StringT const rawName = printing::type::type_name(); CAPTURE(rawName); - printing::type::prettify_identifier( + printing::type::prettify_type( std::ostreambuf_iterator{ss}, rawName); @@ -1263,19 +1263,19 @@ TEST_CASE( } TEST_CASE( - "printing::type::prettify_identifier omits function args with just `void` content.", + "printing::type::prettify_function omits function args with just `void` content.", "[print]") { - StringT const name = "return my_function(void)"; + StringT const name = "ret my_function(void)"; StringStreamT ss{}; - printing::type::prettify_identifier( + printing::type::prettify_function( std::ostreambuf_iterator{ss}, name); REQUIRE_THAT( ss.str(), - Catch::Matchers::Equals(+"return my_function()")); + Catch::Matchers::Equals(+"ret my_function()")); } #endif From 41c33910bb4ae08a45fffecf191563adb9c5c099 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 26 Apr 2025 20:02:56 +0200 Subject: [PATCH 077/130] fix: strenghten the condition for keeping reserved identifiers --- include/mimic++/printing/type/NameParser.hpp | 35 +++++++++----------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 380176fdd..21b9908b5 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -173,38 +173,35 @@ namespace mimicpp::printing::type::parsing [[nodiscard]] constexpr bool keep_reserved_identifier() const noexcept { - if (!m_TokenStack.empty() - && !is_suffix_of(m_TokenStack)) + if (m_TokenStack.empty() + || is_suffix_of(m_TokenStack)) { - return false; - } - - auto const& next = m_Lexer.peek().classification; - if (std::holds_alternative(next)) - { - return true; - } + auto const& next = m_Lexer.peek().classification; + if (std::holds_alternative(next)) + { + return true; + } - if (auto const* op = std::get_if(&next)) - { - return openingAngle == *op - || openingParens == *op - || scopeResolution == *op; + if (auto const* op = std::get_if(&next)) + { + return openingAngle == *op + || openingParens == *op + || scopeResolution == *op; + } } return false; } - constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, lexing::identifier const& identifier) + constexpr void handle_lexer_token(StringViewT const content, lexing::identifier const& identifier) { // Some environments add many reserved symbols (e.g. `__cdecl`). We want to filter out most of these, // but keep those, which are actual names. - // Note: Currently reserved identifiers are only accepted if they are top-level or (template-)functions. - if (!identifier.content.starts_with("__") + if (!content.starts_with("__") || keep_reserved_identifier()) { m_TokenStack.emplace_back( - token::Identifier{.content = identifier.content}); + token::Identifier{.content = identifier.content}); } } From 811e2727db9cd2d80653490fd14eb216084227bb Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Mon, 28 Apr 2025 12:21:04 +0200 Subject: [PATCH 078/130] chore: unify token::Specs reductions --- include/mimic++/printing/type/NameParser.hpp | 15 ++++++-- .../printing/type/NameParserReductions.hpp | 35 +++---------------- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 21b9908b5..e672b3392 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -209,11 +209,19 @@ namespace mimicpp::printing::type::parsing { if (constKeyword == keyword) { - token::add_specs({.isConst = true}, m_TokenStack); + auto& specs = token::get_or_emplace_specs(m_TokenStack); + MIMICPP_ASSERT(!specs.layers.empty(), "Zero spec layers detected."); + auto& top = specs.layers.back(); + MIMICPP_ASSERT(!top.isConst, "Specs is already const."); + top.isConst = true; } else if (volatileKeyword == keyword) { - token::add_specs({.isVolatile = true}, m_TokenStack); + auto& specs = token::get_or_emplace_specs(m_TokenStack); + MIMICPP_ASSERT(!specs.layers.empty(), "Zero spec layers detected."); + auto& top = specs.layers.back(); + MIMICPP_ASSERT(!top.isConst, "Specs is already volatile."); + top.isVolatile = true; } else if (noexceptKeyword == keyword) { @@ -386,7 +394,8 @@ namespace mimicpp::printing::type::parsing } else if (pointer == token) { - token::add_specs_layer(m_TokenStack); + auto& specs = token::get_or_emplace_specs(m_TokenStack); + specs.layers.emplace_back(); } else if (openingAngle == token) { diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 57bc7fdc7..dad943b8e 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -634,44 +634,17 @@ namespace mimicpp::printing::type::parsing [[nodiscard]] constexpr Specs& get_or_emplace_specs(TokenStack& tokenStack) { - if (auto* specs = match_suffix(tokenStack)) + if (is_suffix_of(tokenStack)) { - return *specs; + tokenStack.pop_back(); } - return std::get(tokenStack.emplace_back(Specs{})); - } - - constexpr void add_specs(Specs::Layer newSpecs, TokenStack& tokenStack) - { if (auto* specs = match_suffix(tokenStack)) { - auto& layers = specs->layers; - MIMICPP_ASSERT(!layers.empty(), "Invalid specs state."); - layers.back().merge(newSpecs); - } - else - { - tokenStack.emplace_back( - Specs{.layers = {std::move(newSpecs)}}); + return *specs; } - } - constexpr void add_specs_layer(TokenStack& tokenStack) - { - if (auto* specs = match_suffix(tokenStack)) - { - auto& layers = specs->layers; - MIMICPP_ASSERT(!layers.empty(), "Invalid specs state."); - layers.emplace_back(); - } - else - { - tokenStack.emplace_back( - Specs{ - .layers = {2u, Specs::Layer{}} - }); - } + return std::get(tokenStack.emplace_back(Specs{})); } } } From a035ab4cbc085eb92122a1ebe9a884a84aef062c Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Mon, 28 Apr 2025 13:26:01 +0200 Subject: [PATCH 079/130] fix: make token::Space filtering more reliable --- include/mimic++/printing/type/NameParser.hpp | 39 ++++++++++++++----- .../printing/type/NameParserReductions.hpp | 4 ++ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index e672b3392..cbfd0558a 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -157,16 +157,23 @@ namespace mimicpp::printing::type::parsing constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, [[maybe_unused]] lexing::space const& space) { - // In some cases, a space after an identifier carries semantic meaning. - // I.e. consider these two type-names: `void ()` and `foo()`, - // where the former is a type returning `void` and the latter is a function named `foo`. - // In fact keep all spaces directly before all opening tokens. - if (auto const* nextOp = std::get_if(&m_Lexer.peek().classification); - nextOp - && util::contains(std::array{openingParens, openingAngle, openingCurly, openingSquare, backtick, singleQuote}, *nextOp)) - { - token::try_reduce_as_type(m_TokenStack); - m_TokenStack.emplace_back(token::Space{}); + // 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`. + // Since reliably identifying all cases where the space is meaningful is very difficult, + // we instead focus on safely filtering out cases where we are certain the space has no special meaning. + if (!m_TokenStack.empty()) + { + if (auto const& prev = m_TokenStack.back(); + !std::holds_alternative(prev) + && !std::holds_alternative(prev) + && !std::holds_alternative(prev) + && !std::holds_alternative(prev) + && !std::holds_alternative(prev)) + { + m_TokenStack.emplace_back(token::Space{}); + } } } @@ -416,6 +423,18 @@ namespace mimicpp::printing::type::parsing } else if (openingParens == token) { + // Return types are generally separated by a space from function arguments. + // Try to reduce to a type, but preserve the space at its original position. + // Note: we take special care not to accidentally reduce the actual function identifier. + // This is why we specifically check for the presence of the space. + if (is_suffix_of(m_TokenStack)) + { + if (token::try_reduce_as_type(m_TokenStack)) + { + m_TokenStack.emplace_back(token::Space{}); + } + } + m_TokenStack.emplace_back( std::in_place_type, content); diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index dad943b8e..b164b244c 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -261,6 +261,7 @@ namespace mimicpp::printing::type::parsing { std::span pendingStack{tokenStack}; + ignore_space(pendingStack); Specs* funSpecs{}; if (auto* specs = match_suffix(pendingStack)) { @@ -268,6 +269,7 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingStack, 1u); } + ignore_space(pendingStack); if (auto* funArgs = match_suffix(pendingStack)) { remove_suffix(pendingStack, 1u); @@ -507,6 +509,7 @@ namespace mimicpp::printing::type::parsing { std::span pendingTokens{tokenStack}; + ignore_space(pendingTokens); Specs* suffixSpecs{}; if (auto* specs = match_suffix(pendingTokens)) { @@ -514,6 +517,7 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } + ignore_space(pendingTokens); if (!is_suffix_of(pendingTokens)) { return false; From 8fd321d1156b3991009cbe1523d6934225771e89 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Mon, 28 Apr 2025 17:06:41 +0200 Subject: [PATCH 080/130] test: extend parsing::NameParser test cases --- test/unit-tests/printing/TypeNameParser.cpp | 93 ++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 58c523aa6..d703ad9b7 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1046,7 +1046,43 @@ TEST_CASE( parser.parse_type(); } - SECTION("Qualified placeholders are detected.") + SECTION("Prefix qualified placeholders are detected.") + { + StringT const spec = GENERATE("const", "volatile"); + StringT 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Suffix qualified placeholders are detected.") + { + StringT const spec = GENERATE("const", "volatile", "&", "&&", "*"); + StringT 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Fully qualified placeholders are detected.") { StringT const input = "volatile " + placeholder + " const&"; CAPTURE(input); @@ -1953,6 +1989,61 @@ TEST_CASE( parser.parse_type(); } + SECTION("Prefix qualified identifiers.") + { + StringT const spec = GENERATE("const", "volatile"); + StringT 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Suffix qualified identifiers.") + { + StringT const spec = GENERATE("const", "volatile", "&", "&&", "*"); + StringT 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("Fully qualified identifiers.") + { + StringT 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } + SECTION("As function name.") { StringT const input = identifier + "()"; From 1a364cff53bc83b9c53b87e478edd460fb77c30b Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 28 Apr 2025 18:52:13 +0200 Subject: [PATCH 081/130] chore: rework token::Specs handling --- include/mimic++/printing/type/NameParser.hpp | 36 ++++++++++--- .../printing/type/NameParserReductions.hpp | 53 +++++++++++-------- .../printing/type/NameParserTokens.hpp | 27 ++++++++++ 3 files changed, 89 insertions(+), 27 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index cbfd0558a..28d6de2cd 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -150,6 +150,14 @@ namespace mimicpp::printing::type::parsing unwrapped.unrecognized(m_Content); } + constexpr void pop_space() noexcept + { + if (is_suffix_of(m_TokenStack)) + { + m_TokenStack.pop_back(); + } + } + static constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, [[maybe_unused]] lexing::end const& end) { util::unreachable(); @@ -172,6 +180,11 @@ namespace mimicpp::printing::type::parsing && !std::holds_alternative(prev) && !std::holds_alternative(prev)) { + if (std::holds_alternative(prev)) + { + token::try_reduce_as_type(m_TokenStack); + } + m_TokenStack.emplace_back(token::Space{}); } } @@ -380,8 +393,11 @@ namespace mimicpp::printing::type::parsing } else if (commaSeparator == token) { - token::try_reduce_as_type(m_TokenStack) - && token::try_reduce_as_arg_sequence(m_TokenStack); + 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, @@ -412,8 +428,12 @@ namespace mimicpp::printing::type::parsing } else if (closingAngle == token) { - token::try_reduce_as_type(m_TokenStack) - && token::try_reduce_as_arg_sequence(m_TokenStack); + pop_space(); + 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, @@ -441,8 +461,12 @@ namespace mimicpp::printing::type::parsing } else if (closingParens == token) { - token::try_reduce_as_type(m_TokenStack) - && token::try_reduce_as_arg_sequence(m_TokenStack); + pop_space(); + 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, diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index b164b244c..5f12c0bec 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -148,21 +148,26 @@ namespace mimicpp::printing::type::parsing constexpr bool try_reduce_as_arg_sequence(TokenStack& tokenStack) { - if (std::optional suffix = match_suffix(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(tokenStack.size() - 2u); + tokenStack.resize(pendingTokens.size()); return true; } - if (auto* type = match_suffix(tokenStack)) + if (auto* type = match_suffix(pendingTokens)) { + remove_suffix(pendingTokens, 1u); + ArgSequence seq{}; seq.types.emplace_back(std::move(*type)); - tokenStack.pop_back(); + tokenStack.resize(pendingTokens.size()); tokenStack.emplace_back(std::move(seq)); return true; @@ -509,14 +514,6 @@ namespace mimicpp::printing::type::parsing { std::span pendingTokens{tokenStack}; - ignore_space(pendingTokens); - Specs* suffixSpecs{}; - if (auto* specs = match_suffix(pendingTokens)) - { - suffixSpecs = specs; - remove_suffix(pendingTokens, 1u); - } - ignore_space(pendingTokens); if (!is_suffix_of(pendingTokens)) { @@ -527,11 +524,6 @@ namespace mimicpp::printing::type::parsing .identifier = std::move(std::get(pendingTokens.back()))}; remove_suffix(pendingTokens, 1u); - if (suffixSpecs) - { - newType.specs = std::move(*suffixSpecs); - } - if (auto* seq = match_suffix(pendingTokens)) { newType.scopes = std::move(*seq); @@ -546,7 +538,7 @@ namespace mimicpp::printing::type::parsing MIMICPP_ASSERT(token::Specs::Refness::none == prefixSpecs->refness && !prefixSpecs->isNoexcept, "Invalid prefix specs."); MIMICPP_ASSERT(1u == layers.size(), "Prefix specs can not have more than one layer."); - newType.specs.layers.front().merge(layers.front()); + newType.specs = std::move(*prefixSpecs); remove_suffix(pendingTokens, 1u); } @@ -638,16 +630,35 @@ namespace mimicpp::printing::type::parsing [[nodiscard]] constexpr Specs& get_or_emplace_specs(TokenStack& tokenStack) { - if (is_suffix_of(tokenStack)) + // We probably got something like `type&` and need to reduce that identifier to an actual `Type` token. + if (is_suffix_of(tokenStack)) { - tokenStack.pop_back(); + try_reduce_as_type(tokenStack); + + return std::get(tokenStack.back()).specs(); } - if (auto* specs = match_suffix(tokenStack)) + std::span pendingTokens{tokenStack}; + ignore_space(pendingTokens); + + Specs* specs = match_suffix(pendingTokens); + if (!specs) { + if (auto* const type = match_suffix(pendingTokens)) + { + specs = &type->specs(); + } + } + + if (specs) + { + // Drop trailing space if necessary. + tokenStack.resize(pendingTokens.size()); + return *specs; } + // No specs found yet? Assume prefix specs and leave possibly existing space untouched. return std::get(tokenStack.emplace_back(Specs{})); } } diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp index b77734860..a542e3ed8 100644 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -540,6 +540,14 @@ namespace mimicpp::printing::type::parsing::token && 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 { @@ -549,6 +557,25 @@ namespace mimicpp::printing::type::parsing::token [&](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; From 553b49d841276cb4911f502d3fc5d81d9e5a3dfb Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 28 Apr 2025 18:53:07 +0200 Subject: [PATCH 082/130] chore: parsing::NameParser keeps all identifiers, even the reserved ones --- include/mimic++/printing/type/NameParser.hpp | 35 ++------------------ 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 28d6de2cd..2d1ebad8a 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -190,39 +190,10 @@ namespace mimicpp::printing::type::parsing } } - [[nodiscard]] - constexpr bool keep_reserved_identifier() const noexcept + constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, lexing::identifier const& identifier) { - if (m_TokenStack.empty() - || is_suffix_of(m_TokenStack)) - { - auto const& next = m_Lexer.peek().classification; - if (std::holds_alternative(next)) - { - return true; - } - - if (auto const* op = std::get_if(&next)) - { - return openingAngle == *op - || openingParens == *op - || scopeResolution == *op; - } - } - - return false; - } - - constexpr void handle_lexer_token(StringViewT const content, lexing::identifier const& identifier) - { - // Some environments add many reserved symbols (e.g. `__cdecl`). We want to filter out most of these, - // but keep those, which are actual names. - if (!content.starts_with("__") - || keep_reserved_identifier()) - { - m_TokenStack.emplace_back( - token::Identifier{.content = identifier.content}); - } + m_TokenStack.emplace_back( + token::Identifier{.content = identifier.content}); } constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, lexing::keyword const& keyword) From 04c006f0fd895d3d3e5b3e252f90279a1be2b13c Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 28 Apr 2025 19:13:43 +0200 Subject: [PATCH 083/130] chore: parsing::NameParser does not store Space tokens --- include/mimic++/printing/type/NameParser.hpp | 48 +--------------- .../printing/type/NameParserReductions.hpp | 57 ++++--------------- .../printing/type/NameParserTokens.hpp | 5 -- 3 files changed, 15 insertions(+), 95 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 2d1ebad8a..0943d2a15 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -150,14 +150,6 @@ namespace mimicpp::printing::type::parsing unwrapped.unrecognized(m_Content); } - constexpr void pop_space() noexcept - { - if (is_suffix_of(m_TokenStack)) - { - m_TokenStack.pop_back(); - } - } - static constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, [[maybe_unused]] lexing::end const& end) { util::unreachable(); @@ -169,24 +161,9 @@ namespace mimicpp::printing::type::parsing // For example, consider the type names `void ()` and `foo()`: // - `void ()` represents a function type returning `void`. // - `foo()` represents a function named `foo`. - // Since reliably identifying all cases where the space is meaningful is very difficult, - // we instead focus on safely filtering out cases where we are certain the space has no special meaning. - if (!m_TokenStack.empty()) - { - if (auto const& prev = m_TokenStack.back(); - !std::holds_alternative(prev) - && !std::holds_alternative(prev) - && !std::holds_alternative(prev) - && !std::holds_alternative(prev) - && !std::holds_alternative(prev)) - { - if (std::holds_alternative(prev)) - { - token::try_reduce_as_type(m_TokenStack); - } - - m_TokenStack.emplace_back(token::Space{}); - } + if (is_suffix_of(m_TokenStack)) + { + token::try_reduce_as_type(m_TokenStack); } } @@ -399,7 +376,6 @@ namespace mimicpp::printing::type::parsing } else if (closingAngle == token) { - pop_space(); if (is_suffix_of(m_TokenStack) || token::try_reduce_as_type(m_TokenStack)) { @@ -414,25 +390,12 @@ namespace mimicpp::printing::type::parsing } else if (openingParens == token) { - // Return types are generally separated by a space from function arguments. - // Try to reduce to a type, but preserve the space at its original position. - // Note: we take special care not to accidentally reduce the actual function identifier. - // This is why we specifically check for the presence of the space. - if (is_suffix_of(m_TokenStack)) - { - if (token::try_reduce_as_type(m_TokenStack)) - { - m_TokenStack.emplace_back(token::Space{}); - } - } - m_TokenStack.emplace_back( std::in_place_type, content); } else if (closingParens == token) { - pop_space(); if (is_suffix_of(m_TokenStack) || token::try_reduce_as_type(m_TokenStack)) { @@ -518,11 +481,6 @@ namespace mimicpp::printing::type::parsing m_TokenStack.pop_back(); } - if (is_suffix_of(m_TokenStack)) - { - m_TokenStack.pop_back(); - } - // Ignore return-types. if (is_suffix_of(m_TokenStack) || token::try_reduce_as_type(m_TokenStack)) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 5f12c0bec..9b139e7b6 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -98,14 +98,6 @@ namespace mimicpp::printing::type::parsing { bool try_reduce_as_type(TokenStack& tokenStack); - constexpr void ignore_space(std::span& tokenStack) noexcept - { - if (is_suffix_of(tokenStack)) - { - remove_suffix(tokenStack, 1u); - } - } - inline bool try_reduce_as_scope_sequence(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; @@ -266,7 +258,6 @@ namespace mimicpp::printing::type::parsing { std::span pendingStack{tokenStack}; - ignore_space(pendingStack); Specs* funSpecs{}; if (auto* specs = match_suffix(pendingStack)) { @@ -274,7 +265,6 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingStack, 1u); } - ignore_space(pendingStack); if (auto* funArgs = match_suffix(pendingStack)) { remove_suffix(pendingStack, 1u); @@ -299,8 +289,6 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_function_identifier(TokenStack& tokenStack) { std::span pendingStack{tokenStack}; - ignore_space(pendingStack); - if (std::optional suffix = match_suffix(pendingStack)) { remove_suffix(pendingStack, 2u); @@ -323,9 +311,9 @@ namespace mimicpp::printing::type::parsing 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); @@ -395,8 +383,6 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } - ignore_space(pendingTokens); - if (!is_suffix_of(pendingTokens)) { return false; @@ -431,14 +417,14 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_function_type(TokenStack& tokenStack) { // The space is required, because the return type will always be spaced away from the parens. - if (std::optional suffix = match_suffix(tokenStack)) + if (std::optional suffix = match_suffix(tokenStack)) { - auto& [returnType, space, funCtx] = *suffix; + auto& [returnType, funCtx] = *suffix; FunctionType funType{ .returnType = std::make_shared(std::move(returnType)), .context = std::move(funCtx)}; - tokenStack.resize(tokenStack.size() - 2u); + tokenStack.pop_back(); tokenStack.back().emplace(std::move(funType)); return true; @@ -454,9 +440,9 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_function_ptr_type(TokenStack& tokenStack) { - if (std::optional suffix = match_suffix(tokenStack)) + if (std::optional suffix = match_suffix(tokenStack)) { - auto& [returnType, space, ptr, ctx] = *suffix; + auto& [returnType, ptr, ctx] = *suffix; std::optional nestedInfo = std::move(ptr.nested); FunctionPtrType ptrType{ @@ -465,7 +451,7 @@ namespace mimicpp::printing::type::parsing .specs = std::move(ptr.specs), .context = std::move(ctx)}; - tokenStack.resize(tokenStack.size() - 3); + tokenStack.resize(tokenStack.size() - 2u); tokenStack.back().emplace(std::move(ptrType)); // We got something like `ret (*(outer-args))(args)` or `ret (*(*)(outer-args))(args)`, where the currently @@ -488,9 +474,6 @@ namespace mimicpp::printing::type::parsing { 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) { @@ -513,8 +496,6 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_regular_type(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; - - ignore_space(pendingTokens); if (!is_suffix_of(pendingTokens)) { return false; @@ -530,8 +511,6 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } - // When ScopeSequence or Identifier starts with a placeholder-like, there will be an additional space-token. - ignore_space(pendingTokens); if (auto* prefixSpecs = match_suffix(pendingTokens)) { auto& layers = prefixSpecs->layers; @@ -580,8 +559,6 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } - // When ScopeSequence or Identifier starts with a placeholder-like, there will be an additional space-token. - ignore_space(pendingTokens); if (auto* returnType = match_suffix(pendingTokens)) { function.returnType = std::make_shared(std::move(*returnType)); @@ -638,27 +615,17 @@ namespace mimicpp::printing::type::parsing return std::get(tokenStack.back()).specs(); } - std::span pendingTokens{tokenStack}; - ignore_space(pendingTokens); - - Specs* specs = match_suffix(pendingTokens); - if (!specs) + if (auto* specs = match_suffix(tokenStack)) { - if (auto* const type = match_suffix(pendingTokens)) - { - specs = &type->specs(); - } + return *specs; } - if (specs) + if (auto* const type = match_suffix(tokenStack)) { - // Drop trailing space if necessary. - tokenStack.resize(pendingTokens.size()); - - return *specs; + return type->specs(); } - // No specs found yet? Assume prefix specs and leave possibly existing space untouched. + // No specs found yet? Assume prefix specs. return std::get(tokenStack.emplace_back(Specs{})); } } diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp index a542e3ed8..5841f850e 100644 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -76,10 +76,6 @@ namespace mimicpp::printing::type::parsing::token { class Type; - class Space - { - }; - class OperatorKeyword { }; @@ -648,7 +644,6 @@ namespace mimicpp::printing::type::parsing::token namespace mimicpp::printing::type::parsing { using Token = std::variant< - token::Space, token::OperatorKeyword, token::ScopeResolution, token::ArgSeparator, From 8b4ac27a22ca09dedcb1403e4357186b938978e6 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 28 Apr 2025 19:26:48 +0200 Subject: [PATCH 084/130] chore: rework token::FunctionContext and remove token::FunctionArgs --- include/mimic++/printing/type/NameParser.hpp | 8 +-- .../printing/type/NameParserReductions.hpp | 55 +++++-------------- .../printing/type/NameParserTokens.hpp | 21 +------ 3 files changed, 18 insertions(+), 66 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 0943d2a15..ec0ced1d2 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -48,7 +48,6 @@ namespace mimicpp::printing::type::parsing constexpr void parse_function() { parse(); - token::try_reduce_as_function_context(m_TokenStack); if (m_HasConversionOperator) { @@ -331,8 +330,7 @@ namespace mimicpp::printing::type::parsing { if (scopeResolution == token) { - token::try_reduce_as_function_context(m_TokenStack) - && token::try_reduce_as_function_identifier(m_TokenStack); + token::try_reduce_as_function_identifier(m_TokenStack); m_TokenStack.emplace_back( std::in_place_type, @@ -406,7 +404,7 @@ namespace mimicpp::printing::type::parsing std::in_place_type, content); - token::try_reduce_as_function_args(m_TokenStack) + token::try_reduce_as_function_context(m_TokenStack) || token::try_reduce_as_function_ptr(m_TokenStack) || token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); } @@ -431,8 +429,6 @@ namespace mimicpp::printing::type::parsing } else if (singleQuote == token) { - token::try_reduce_as_function_context(m_TokenStack); - if (token::try_reduce_as_function_identifier(m_TokenStack)) { unwrap_msvc_like_function(); diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 9b139e7b6..e0be79ca5 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -209,7 +209,7 @@ namespace mimicpp::printing::type::parsing return true; } - constexpr bool try_reduce_as_function_args(TokenStack& tokenStack) + constexpr bool try_reduce_as_function_context(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; if (!is_suffix_of(pendingTokens)) @@ -237,55 +237,23 @@ namespace mimicpp::printing::type::parsing return false; } - FunctionArgs funArgs{}; + FunctionContext funCtx{}; if (args) { // We omit function args with only `void`. if (1u != args->types.size() || !args->types.front().is_void()) { - funArgs.args = std::move(*args); + funCtx.args = std::move(*args); } } tokenStack.resize(pendingTokens.size()); - tokenStack.emplace_back(std::move(funArgs)); + tokenStack.emplace_back(std::move(funCtx)); return true; } - constexpr bool try_reduce_as_function_context(TokenStack& tokenStack) - { - std::span pendingStack{tokenStack}; - - Specs* funSpecs{}; - if (auto* specs = match_suffix(pendingStack)) - { - funSpecs = specs; - remove_suffix(pendingStack, 1u); - } - - if (auto* funArgs = match_suffix(pendingStack)) - { - remove_suffix(pendingStack, 1u); - - FunctionContext funContext{ - .args = std::move(*funArgs)}; - - if (funSpecs) - { - funContext.specs = std::move(*funSpecs); - } - - tokenStack.resize(pendingStack.size()); - tokenStack.emplace_back(std::move(funContext)); - - return true; - } - - return false; - } - inline bool try_reduce_as_function_identifier(TokenStack& tokenStack) { std::span pendingStack{tokenStack}; @@ -537,8 +505,6 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_type(TokenStack& tokenStack) { - token::try_reduce_as_function_context(tokenStack); - return try_reduce_as_function_ptr_type(tokenStack) || try_reduce_as_function_type(tokenStack) || try_reduce_as_regular_type(tokenStack); @@ -615,14 +581,19 @@ namespace mimicpp::printing::type::parsing return std::get(tokenStack.back()).specs(); } - if (auto* specs = match_suffix(tokenStack)) + if (auto* const type = match_suffix(tokenStack)) { - return *specs; + return type->specs(); } - if (auto* const type = match_suffix(tokenStack)) + if (auto* const ctx = match_suffix(tokenStack)) { - return type->specs(); + return ctx->specs; + } + + if (auto* specs = match_suffix(tokenStack)) + { + return *specs; } // No specs found yet? Assume prefix specs. diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp index 5841f850e..dca1344e5 100644 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -253,22 +253,6 @@ namespace mimicpp::printing::type::parsing::token constexpr void handle_as_template_args(Visitor& visitor) const; }; - class FunctionArgs - { - public: - ArgSequence args{}; - - template - constexpr void operator()(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - unwrapped.begin_function_args(); - std::invoke(args, unwrapped); - unwrapped.end_function_args(); - } - }; - class Identifier { public: @@ -351,7 +335,7 @@ namespace mimicpp::printing::type::parsing::token class FunctionContext { public: - FunctionArgs args{}; + ArgSequence args{}; Specs specs{}; template @@ -359,7 +343,9 @@ namespace mimicpp::printing::type::parsing::token { auto& unwrapped = unwrap_visitor(visitor); + unwrapped.begin_function_args(); std::invoke(args, unwrapped); + unwrapped.end_function_args(); std::invoke(specs, unwrapped); } }; @@ -661,7 +647,6 @@ namespace mimicpp::printing::type::parsing token::FunctionIdentifier, token::ScopeSequence, token::ArgSequence, - token::FunctionArgs, token::FunctionContext, token::FunctionPtr, token::Specs, From 9d345488c9fe6c3279cecab7203382e145d601c7 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 28 Apr 2025 21:40:57 +0200 Subject: [PATCH 085/130] fix: parsing::NameParser never accepts two adjacent Type tokens --- .../printing/type/NameParserReductions.hpp | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index e0be79ca5..0455ccd99 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -464,31 +464,47 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_regular_type(TokenStack& tokenStack) { std::span pendingTokens{tokenStack}; - if (!is_suffix_of(pendingTokens)) + auto* const identifier = match_suffix(pendingTokens); + if (!identifier) { return false; } - - RegularType newType{ - .identifier = std::move(std::get(pendingTokens.back()))}; remove_suffix(pendingTokens, 1u); - if (auto* seq = match_suffix(pendingTokens)) + auto* const scopes = match_suffix(pendingTokens); + if (scopes) { - newType.scopes = std::move(*seq); remove_suffix(pendingTokens, 1u); } - if (auto* prefixSpecs = match_suffix(pendingTokens)) + auto* const prefixSpecs = match_suffix(pendingTokens); + if (prefixSpecs) { - auto& layers = prefixSpecs->layers; + [[maybe_unused]] auto& layers = prefixSpecs->layers; MIMICPP_ASSERT(token::Specs::Refness::none == prefixSpecs->refness && !prefixSpecs->isNoexcept, "Invalid prefix specs."); MIMICPP_ASSERT(1u == layers.size(), "Prefix specs can not have more than one layer."); - newType.specs = std::move(*prefixSpecs); 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)) { From 2c160eb0ef34f7bd9a8fd1c125dbb5302d5c95d3 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 29 Apr 2025 11:51:10 +0200 Subject: [PATCH 086/130] fix: parsing::NameParser ignores non-name reserved-identifiers --- include/mimic++/printing/type/NameParser.hpp | 11 ++- .../printing/type/NameParserReductions.hpp | 68 ++++++++++++------- .../printing/type/NameParserTokens.hpp | 11 ++- 3 files changed, 64 insertions(+), 26 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index ec0ced1d2..392e39b87 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -477,9 +477,16 @@ namespace mimicpp::printing::type::parsing m_TokenStack.pop_back(); } + // Ignore call-convention. + if (auto const* const id = match_suffix(m_TokenStack); + id + && id->is_reserved()) + { + m_TokenStack.pop_back(); + } + // Ignore return-types. - if (is_suffix_of(m_TokenStack) - || token::try_reduce_as_type(m_TokenStack)) + if (is_suffix_of(m_TokenStack)) { m_TokenStack.pop_back(); } diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 0455ccd99..8c49dbcdf 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -94,6 +94,16 @@ namespace mimicpp::printing::type::parsing tokenStack = tokenStack.first(tokenStack.size() - count); } + 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); @@ -257,6 +267,10 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_function_identifier(TokenStack& tokenStack) { std::span pendingStack{tokenStack}; + + // Ignore something like `__ptr64` on msvc. + ignore_reserved_identifier(pendingStack); + if (std::optional suffix = match_suffix(pendingStack)) { remove_suffix(pendingStack, 2u); @@ -351,6 +365,9 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } + // Ignore call-convention. + ignore_reserved_identifier(pendingTokens); + if (!is_suffix_of(pendingTokens)) { return false; @@ -384,21 +401,34 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_function_type(TokenStack& tokenStack) { - // The space is required, because the return type will always be spaced away from the parens. - if (std::optional suffix = match_suffix(tokenStack)) + std::span pendingTokens{tokenStack}; + + auto* const funCtx = match_suffix(pendingTokens); + if (!funCtx) { - auto& [returnType, funCtx] = *suffix; - FunctionType funType{ - .returnType = std::make_shared(std::move(returnType)), - .context = std::move(funCtx)}; + return false; + } + remove_suffix(pendingTokens, 1u); - tokenStack.pop_back(); - tokenStack.back().emplace(std::move(funType)); + // Ignore call-convention. + ignore_reserved_identifier(pendingTokens); - return true; + auto* const returnType = match_suffix(pendingTokens); + if (!returnType) + { + return false; } + remove_suffix(pendingTokens, 1u); - return false; + FunctionType funType{ + .returnType = std::make_shared(std::move(*returnType)), + .context = std::move(*funCtx)}; + + tokenStack.resize( + std::exchange(pendingTokens, {}).size() + 1u); + tokenStack.back().emplace(std::move(funType)); + + return true; } namespace detail @@ -541,6 +571,9 @@ namespace mimicpp::printing::type::parsing 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)); @@ -549,16 +582,6 @@ namespace mimicpp::printing::type::parsing tokenStack.resize( std::exchange(pendingTokens, {}).size()); - - // There may be something similar to a return type in front, which hasn't been reduced yet. - if (!function.returnType - && try_reduce_as_type(tokenStack)) - { - function.returnType = std::make_shared( - std::get(std::move(tokenStack.back()))); - tokenStack.pop_back(); - } - tokenStack.emplace_back(std::move(function)); return true; @@ -590,10 +613,9 @@ namespace mimicpp::printing::type::parsing constexpr Specs& get_or_emplace_specs(TokenStack& tokenStack) { // We probably got something like `type&` and need to reduce that identifier to an actual `Type` token. - if (is_suffix_of(tokenStack)) + if (is_suffix_of(tokenStack) + && try_reduce_as_type(tokenStack)) { - try_reduce_as_type(tokenStack); - return std::get(tokenStack.back()).specs(); } diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp index dca1344e5..34c706270 100644 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -275,12 +275,21 @@ namespace mimicpp::printing::type::parsing::token [[nodiscard]] constexpr bool is_void() const noexcept { - auto const* id = std::get_if(&content); + 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("__"); + } + template constexpr void operator()(Visitor& visitor) const { From c2dca68d0cd45f109eee9f4958ac5f8eda46f4b5 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 29 Apr 2025 12:10:22 +0200 Subject: [PATCH 087/130] fix: define functions type::prettify_function and type::prettify_type when MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES is disabled --- include/mimic++/printing/type/PostProcessing.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/include/mimic++/printing/type/PostProcessing.hpp b/include/mimic++/printing/type/PostProcessing.hpp index 2d3df2a00..444b76260 100644 --- a/include/mimic++/printing/type/PostProcessing.hpp +++ b/include/mimic++/printing/type/PostProcessing.hpp @@ -51,7 +51,15 @@ namespace mimicpp::printing::type namespace mimicpp::printing::type { template - constexpr OutIter prettify_identifier(OutIter out, StringT name) + constexpr OutIter prettify_type(OutIter out, StringT name) + { + out = std::ranges::copy(name, std::move(out)).out; + + return out; + } + + template + constexpr OutIter prettify_function(OutIter out, StringT name) { out = std::ranges::copy(name, std::move(out)).out; From d5e70bd5e77e753ceb49f6ac4283ec63a377c02f Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 29 Apr 2025 13:08:14 +0200 Subject: [PATCH 088/130] test: add additional msvc-like test --- test/unit-tests/printing/TypeNameParser.cpp | 48 ++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index d703ad9b7..50c93d8cd 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1795,13 +1795,14 @@ TEST_CASE( ScopedSequence sequence{}; sequence += visitor.begin.expect_call(); - sequence += visitor.begin_type.expect_call(); SECTION("When function local type is given.") { StringT 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(); @@ -1836,6 +1837,8 @@ TEST_CASE( "`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(); @@ -1933,6 +1936,8 @@ TEST_CASE( StringT const input = StringT{typeClass} + " `" + StringT{visibility} + ": void __cdecl foo() __ptr64" + spacing + "'::my_type"; CAPTURE(input); + sequence += visitor.begin_type.expect_call(); + sequence += visitor.begin_scope.expect_call(); { sequence += visitor.begin_function.expect_call(); @@ -1960,6 +1965,47 @@ TEST_CASE( printing::type::parsing::NameParser parser{std::ref(visitor), input}; parser.parse_type(); } + + SECTION("When decorated lambda is given.") + { + StringT 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(); + + sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.add_const.expect_call(); + + sequence += visitor.end_function.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_function(); + } } TEST_CASE( From aad44a3406a610eb209a52567b3dd15e7f5eef67 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 29 Apr 2025 13:46:12 +0200 Subject: [PATCH 089/130] fix: parsing::NameParser does not treat placeholders as template- or function-args --- include/mimic++/printing/type/NameParser.hpp | 11 ++- .../printing/type/NameParserReductions.hpp | 93 +++++++++++-------- .../printing/type/NameParserTokens.hpp | 5 + 3 files changed, 70 insertions(+), 39 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 392e39b87..f5b168776 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -156,13 +156,20 @@ namespace mimicpp::printing::type::parsing constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, [[maybe_unused]] lexing::space const& space) { + if (is_suffix_of(m_TokenStack)) + { + 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 (is_suffix_of(m_TokenStack)) + if (auto const* const nextOp = std::get_if(&m_Lexer.peek().classification); + nextOp + && util::contains(std::array{openingAngle, openingParens}, *nextOp)) { - token::try_reduce_as_type(m_TokenStack); + m_TokenStack.emplace_back(token::Space{}); } } diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 8c49dbcdf..88a5d7650 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -94,6 +94,14 @@ namespace mimicpp::printing::type::parsing 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); @@ -293,6 +301,7 @@ namespace mimicpp::printing::type::parsing 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) @@ -323,8 +332,46 @@ namespace mimicpp::printing::type::parsing StringViewT const content{opening.content.data(), contentLength}; pendingTokens = std::span{pendingTokens.begin(), openingIter.base() - 1}; - tokenStack.resize(pendingTokens.size()); - tokenStack.emplace_back(Identifier{.content = content}); + + // 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}; + + // The return type is always delimited by space from the arg-list. + std::optional suffix = match_suffix(pendingTokens); + if (!suffix) + { + return false; + } + remove_suffix(pendingTokens, 2u); + + // Ignore call-convention. + ignore_reserved_identifier(pendingTokens); + + auto* const returnType = match_suffix(pendingTokens); + if (!returnType) + { + return false; + } + remove_suffix(pendingTokens, 1u); + + auto& [space, ctx] = *suffix; + 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; } @@ -399,38 +446,6 @@ namespace mimicpp::printing::type::parsing return true; } - inline bool try_reduce_as_function_type(TokenStack& tokenStack) - { - std::span pendingTokens{tokenStack}; - - auto* const funCtx = match_suffix(pendingTokens); - if (!funCtx) - { - 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(*funCtx)}; - - tokenStack.resize( - std::exchange(pendingTokens, {}).size() + 1u); - tokenStack.back().emplace(std::move(funType)); - - return true; - } - namespace detail { void handled_nested_function_ptr(TokenStack& tokenStack, FunctionPtr::NestedInfo info); @@ -438,9 +453,10 @@ namespace mimicpp::printing::type::parsing inline bool try_reduce_as_function_ptr_type(TokenStack& tokenStack) { - if (std::optional suffix = match_suffix(tokenStack)) + // The return type is always delimited by space from the spec part. + if (std::optional suffix = match_suffix(tokenStack)) { - auto& [returnType, ptr, ctx] = *suffix; + auto& [returnType, space, ptr, ctx] = *suffix; std::optional nestedInfo = std::move(ptr.nested); FunctionPtrType ptrType{ @@ -449,7 +465,7 @@ namespace mimicpp::printing::type::parsing .specs = std::move(ptr.specs), .context = std::move(ctx)}; - tokenStack.resize(tokenStack.size() - 2u); + tokenStack.resize(tokenStack.size() - 3u); tokenStack.back().emplace(std::move(ptrType)); // We got something like `ret (*(outer-args))(args)` or `ret (*(*)(outer-args))(args)`, where the currently @@ -472,6 +488,9 @@ namespace mimicpp::printing::type::parsing { 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) { diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp index 34c706270..6ebe3d0cd 100644 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -76,6 +76,10 @@ namespace mimicpp::printing::type::parsing::token { class Type; + class Space + { + }; + class OperatorKeyword { }; @@ -639,6 +643,7 @@ namespace mimicpp::printing::type::parsing::token namespace mimicpp::printing::type::parsing { using Token = std::variant< + token::Space, token::OperatorKeyword, token::ScopeResolution, token::ArgSeparator, From 1372b60a28d39921b0abfe67cdf308f7e9cba9c4 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Tue, 29 Apr 2025 12:47:18 +0200 Subject: [PATCH 090/130] fix: treat token::TypeContext as valid placeholder-prefix --- include/mimic++/printing/type/NameParserReductions.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 88a5d7650..f1066ca30 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -305,6 +305,7 @@ namespace mimicpp::printing::type::parsing || 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); From 2b3a2b2f5f12e1815856863f6e0c008c40bf0353 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Tue, 29 Apr 2025 14:20:48 +0200 Subject: [PATCH 091/130] fix: parsing::NameParser correctly handles placeholders --- include/mimic++/printing/type/NameParser.hpp | 8 +++++++- include/mimic++/printing/type/NameParserReductions.hpp | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index f5b168776..0610e4fae 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -167,7 +167,7 @@ namespace mimicpp::printing::type::parsing // - `foo()` represents a function named `foo`. if (auto const* const nextOp = std::get_if(&m_Lexer.peek().classification); nextOp - && util::contains(std::array{openingAngle, openingParens}, *nextOp)) + && util::contains(std::array{openingAngle, openingParens, openingCurly, singleQuote, backtick}, *nextOp)) { m_TokenStack.emplace_back(token::Space{}); } @@ -501,6 +501,12 @@ namespace mimicpp::printing::type::parsing MIMICPP_ASSERT(match_suffix(m_TokenStack), "Invalid state."); 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(); + } + if (auto* targetScopes = match_suffix(m_TokenStack)) { auto& target = targetScopes->scopes; diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index f1066ca30..d70ff9c9f 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -276,6 +276,9 @@ namespace mimicpp::printing::type::parsing { 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); From 0d9224fea77e2125a15a12e541a51587b56257ca Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Tue, 29 Apr 2025 14:43:14 +0200 Subject: [PATCH 092/130] fix: parsing::NameParser ignores identifiers like `__ptr64` during type reductions --- .../printing/type/NameParserReductions.hpp | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index d70ff9c9f..60c93690c 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -524,6 +524,16 @@ namespace mimicpp::printing::type::parsing } 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) { @@ -635,11 +645,17 @@ namespace mimicpp::printing::type::parsing [[nodiscard]] constexpr Specs& get_or_emplace_specs(TokenStack& tokenStack) { - // We probably got something like `type&` and need to reduce that identifier to an actual `Type` token. - if (is_suffix_of(tokenStack) - && try_reduce_as_type(tokenStack)) + // Maybe wo got something like `type&` and need to reduce that identifier to an actual `Type` token. + if (is_suffix_of(tokenStack)) { - return std::get(tokenStack.back()).specs(); + 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. + MIMICPP_ASSERT(std::get(tokenStack.back()).is_reserved(), "Unexpected token."); + tokenStack.pop_back(); } if (auto* const type = match_suffix(tokenStack)) From d428e7bba47e9551be96faa4aea0254ca1b1b951 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Tue, 29 Apr 2025 15:24:51 +0200 Subject: [PATCH 093/130] fix: parsing::NameParser treats call-conventions as valid return-type delimiter when reducing function-types --- .../printing/type/NameParserReductions.hpp | 50 +++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 60c93690c..4baef1524 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -350,13 +350,29 @@ namespace mimicpp::printing::type::parsing { std::span pendingTokens{tokenStack}; - // The return type is always delimited by space from the arg-list. - std::optional suffix = match_suffix(pendingTokens); - if (!suffix) + auto* const ctx = match_suffix(pendingTokens); + if (!ctx) { return false; } - remove_suffix(pendingTokens, 2u); + 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); @@ -368,10 +384,9 @@ namespace mimicpp::printing::type::parsing } remove_suffix(pendingTokens, 1u); - auto& [space, ctx] = *suffix; FunctionType funType{ .returnType = std::make_shared(std::move(*returnType)), - .context = std::move(ctx)}; + .context = std::move(*ctx)}; tokenStack.resize( std::exchange(pendingTokens, {}).size() + 1u); @@ -416,8 +431,16 @@ namespace mimicpp::printing::type::parsing remove_suffix(pendingTokens, 1u); } - // Ignore call-convention. - ignore_reserved_identifier(pendingTokens); + // Ignore call-convention, which is already reduced to a type. + if (auto const* const type = match_suffix(pendingTokens)) + { + if (auto const* const id = std::get_if(&type->state); + id + && id->identifier.is_reserved()) + { + remove_suffix(pendingTokens, 1u); + } + } if (!is_suffix_of(pendingTokens)) { @@ -457,9 +480,15 @@ namespace mimicpp::printing::type::parsing 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(tokenStack)) + if (std::optional suffix = match_suffix(pendingTokens)) { + remove_suffix(pendingTokens, 4u); auto& [returnType, space, ptr, ctx] = *suffix; std::optional nestedInfo = std::move(ptr.nested); @@ -469,7 +498,8 @@ namespace mimicpp::printing::type::parsing .specs = std::move(ptr.specs), .context = std::move(ctx)}; - tokenStack.resize(tokenStack.size() - 3u); + 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 From 38caefb8d5115d22ce6b03a617d17df641627dd0 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Mon, 5 May 2025 13:39:25 +0200 Subject: [PATCH 094/130] fix: parsing::NameParser detects function-ptrs decorated with call-convention --- include/mimic++/printing/type/NameParser.hpp | 27 +++++++--- .../printing/type/NameParserReductions.hpp | 53 +++++++++++++------ 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 0610e4fae..65d01a707 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -401,19 +401,34 @@ namespace mimicpp::printing::type::parsing } else if (closingParens == token) { - if (is_suffix_of(m_TokenStack) - || token::try_reduce_as_type(m_TokenStack)) + bool const isNextOpeningParens = std::invoke( + [this] { + auto const* const nextOp = std::get_if(&m_Lexer.peek().classification); + return nextOp && 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) { - token::try_reduce_as_arg_sequence(m_TokenStack); + 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_function_context(m_TokenStack) - || token::try_reduce_as_function_ptr(m_TokenStack) - || token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); + 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) { diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 4baef1524..2c3a35080 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -418,28 +418,49 @@ namespace mimicpp::printing::type::parsing } auto* specs = match_suffix(pendingTokens); - if (!specs - || !specs->has_ptr()) - { - return false; - } - remove_suffix(pendingTokens, 1u); - - auto* scopeSeq = match_suffix(pendingTokens); - if (match_suffix(pendingTokens)) + ScopeSequence* scopeSeq{}; + if (specs && specs->has_ptr()) { remove_suffix(pendingTokens, 1u); - } - // Ignore call-convention, which is already reduced to a type. - if (auto const* const type = match_suffix(pendingTokens)) - { - if (auto const* const id = std::get_if(&type->state); - id - && id->identifier.is_reserved()) + 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)) From 2b79ef64c5111ffca4ddc4d3ecc4384f6b077671 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Mon, 5 May 2025 14:31:25 +0200 Subject: [PATCH 095/130] fix: lexing::NameLexer recognizes `static` as keyword --- include/mimic++/printing/type/NameLexer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mimic++/printing/type/NameLexer.hpp b/include/mimic++/printing/type/NameLexer.hpp index f40fdc00c..639325681 100644 --- a/include/mimic++/printing/type/NameLexer.hpp +++ b/include/mimic++/printing/type/NameLexer.hpp @@ -46,7 +46,7 @@ namespace mimicpp::printing::type::lexing { // just list the noteworthy ones here constexpr std::array visibilityKeywords = std::to_array({"public", "protected", "private"}); - constexpr std::array specKeywords = std::to_array({"const", "constexpr", "volatile", "noexcept"}); + constexpr std::array specKeywords = std::to_array({"const", "constexpr", "volatile", "noexcept", "static"}); constexpr std::array contextKeywords = std::to_array({"operator", "struct", "class", "enum"}); constexpr std::array otherKeywords = std::to_array({"new", "delete", "co_await"}); constexpr std::array digraphs = std::to_array({"and", "or", "xor", "not", "bitand", "bitor", "compl", "and_eq", "or_eq", "xor_eq", "not_eq"}); From 679b57f23ccdf29160abeee4d9a14eb4e91a3d39 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 5 May 2025 14:45:44 +0200 Subject: [PATCH 096/130] test: extend parsing::NameParser test with msvc-like case --- include/mimic++/printing/type/NameParser.hpp | 2 +- test/unit-tests/printing/TypeNameParser.cpp | 21 +++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 65d01a707..25b8ce68b 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -455,7 +455,7 @@ namespace mimicpp::printing::type::parsing { unwrap_msvc_like_function(); } - // Something like `id1::id2 should become id1::id2, so just remove the leading backtick. + // 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); diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 50c93d8cd..7e0831541 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1787,7 +1787,7 @@ TEST_CASE( } TEST_CASE( - "parsing::NameParser handles msvc-like function scopes.", + "parsing::NameParser handles msvc-like wrapped scopes.", "[print][print::type]") { StringT const spacing = GENERATE("", " "); @@ -1796,6 +1796,25 @@ TEST_CASE( sequence += visitor.begin.expect_call(); + SECTION("When scoped identifier is given, it's unwrapped.") + { + StringT const input = spacing + "`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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } + SECTION("When function local type is given.") { StringT const input{"`void foo()" + spacing + "'::my_type"}; From 032f48252af88914646994882ff846f05f97c146 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 5 May 2025 15:01:32 +0200 Subject: [PATCH 097/130] fix: correct test-case from previous commit --- test/unit-tests/printing/TypeNameParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 7e0831541..971e80750 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1798,7 +1798,7 @@ TEST_CASE( SECTION("When scoped identifier is given, it's unwrapped.") { - StringT const input = spacing + "`foo::bar'"; + StringT const input = "`foo::bar'"; sequence += visitor.begin_type.expect_call(); From 9c42fbea91a6574871539da8d2dc1260611d4503 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Mon, 5 May 2025 15:01:32 +0200 Subject: [PATCH 098/130] fix: please gcov --- include/mimic++/printing/type/NameParser.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 25b8ce68b..96b562d71 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -401,11 +401,11 @@ namespace mimicpp::printing::type::parsing } else if (closingParens == token) { - bool const isNextOpeningParens = std::invoke( - [this] { - auto const* const nextOp = std::get_if(&m_Lexer.peek().classification); - return nextOp && openingParens == *nextOp; - }); + bool isNextOpeningParens{false}; + if (auto const* const nextOp = std::get_if(&m_Lexer.peek().classification)) + { + 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. From 352a74fad4be327a1dd421cd9534f9c4c55270d5 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 13:55:20 +0200 Subject: [PATCH 099/130] fix: remove unused value --- include/mimic++/printing/type/NameParser.hpp | 22 +++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 96b562d71..79b602647 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -124,20 +124,18 @@ namespace mimicpp::printing::type::parsing template constexpr bool finalize() { - auto& unwrapped = unwrap_visitor(m_Visitor); - - if (1u == m_TokenStack.size()) + if (1u == m_TokenStack.size() + && std::holds_alternative(m_TokenStack.back())) { - if (auto const* const end = std::get_if(&m_TokenStack.back())) - { - unwrapped.begin(); - std::invoke( - std::get(m_TokenStack.back()), - m_Visitor); - unwrapped.end(); + auto& unwrapped = unwrap_visitor(m_Visitor); - return true; - } + unwrapped.begin(); + std::invoke( + std::get(m_TokenStack.back()), + m_Visitor); + unwrapped.end(); + + return true; } return false; From 798f1fd7a65509475758f24cee1ca6a5b440c060 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 13:57:56 +0200 Subject: [PATCH 100/130] cleanup: remove obsolete parsing::token::Specs::Layer::merge function --- include/mimic++/printing/type/NameParserTokens.hpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp index 6ebe3d0cd..22149a7b2 100644 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -158,15 +158,6 @@ namespace mimicpp::printing::type::parsing::token bool isConst{false}; bool isVolatile{false}; - constexpr void merge(Layer const& others) noexcept - { - MIMICPP_ASSERT(!(isConst && others.isConst), "Merging same specs."); - MIMICPP_ASSERT(!(isVolatile && others.isVolatile), "Merging same specs."); - - isConst = isConst || others.isConst; - isVolatile = isVolatile || others.isVolatile; - } - template constexpr void operator()(Visitor& visitor) const { From 37bddfeb6e4ab3cc293244bfe8dc0d93ece9f667 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 14:10:05 +0200 Subject: [PATCH 101/130] cleanup: remove obsolete character predicates --- include/mimic++/printing/type/NameLexer.hpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/include/mimic++/printing/type/NameLexer.hpp b/include/mimic++/printing/type/NameLexer.hpp index 639325681..086cda74e 100644 --- a/include/mimic++/printing/type/NameLexer.hpp +++ b/include/mimic++/printing/type/NameLexer.hpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -24,22 +23,14 @@ namespace mimicpp::printing::type::lexing { // see: https://en.cppreference.com/w/cpp/string/byte/isspace constexpr auto is_space = [](char const c) noexcept { - return static_cast(std::isspace(static_cast(c))); - }; - - // see: https://en.cppreference.com/w/cpp/string/byte/isalpha - constexpr auto is_digit = [](char const c) noexcept { - return static_cast(std::isdigit(static_cast(c))); + return static_cast( + std::isspace(static_cast(c))); }; // see: https://en.cppreference.com/w/cpp/string/byte/isdigit - constexpr auto is_alpha = [](char const c) noexcept { - return static_cast(std::isdigit(static_cast(c))); - }; - - // see: https://en.cppreference.com/w/cpp/string/byte/isxdigit - constexpr auto is_hex_digit = [](char const c) noexcept { - return static_cast(std::isxdigit(static_cast(c))); + constexpr auto is_digit = [](char const c) noexcept { + return static_cast( + std::isdigit(static_cast(c))); }; namespace texts From b38f5b6d8a92f6acdc06b50cc03e156d4266bad4 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 14:15:26 +0200 Subject: [PATCH 102/130] chore: improve parsing::NameParser::finalize --- include/mimic++/printing/type/NameParser.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 79b602647..c77165080 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -124,18 +124,18 @@ namespace mimicpp::printing::type::parsing template constexpr bool finalize() { - if (1u == m_TokenStack.size() - && std::holds_alternative(m_TokenStack.back())) + if (1u == m_TokenStack.size()) { - auto& unwrapped = unwrap_visitor(m_Visitor); + if (auto const* const end = std::get_if(&m_TokenStack.back())) + { + auto& unwrapped = unwrap_visitor(m_Visitor); - unwrapped.begin(); - std::invoke( - std::get(m_TokenStack.back()), - m_Visitor); - unwrapped.end(); + unwrapped.begin(); + std::invoke(*end, m_Visitor); + unwrapped.end(); - return true; + return true; + } } return false; From ad113a27a88383493b50cb5bcfac0ffea752d643 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 14:26:55 +0200 Subject: [PATCH 103/130] refactor: simplify several token peeks --- include/mimic++/printing/type/NameParser.hpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index c77165080..e512ebb34 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -109,6 +109,12 @@ namespace mimicpp::printing::type::parsing std::vector m_TokenStack{}; + template + constexpr LexerTokenClass const* peek_if() const noexcept + { + return std::get_if(&m_Lexer.peek().classification); + } + constexpr void parse() { for (lexing::token next = m_Lexer.next(); @@ -163,7 +169,7 @@ namespace mimicpp::printing::type::parsing // 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 = std::get_if(&m_Lexer.peek().classification); + if (auto const* const nextOp = peek_if(); nextOp && util::contains(std::array{openingAngle, openingParens, openingCurly, singleQuote, backtick}, *nextOp)) { @@ -262,7 +268,7 @@ namespace mimicpp::printing::type::parsing { dropSpaceInput(); - if (auto const* nextOp = std::get_if(&m_Lexer.peek().classification); + 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)) @@ -303,7 +309,7 @@ namespace mimicpp::printing::type::parsing { dropSpaceInput(); - if (auto* opAfter = std::get_if(&m_Lexer.peek().classification); + if (auto const* const opAfter = peek_if(); opAfter && openingSquare == *opAfter) { @@ -400,9 +406,9 @@ namespace mimicpp::printing::type::parsing else if (closingParens == token) { bool isNextOpeningParens{false}; - if (auto const* const nextOp = std::get_if(&m_Lexer.peek().classification)) + if (auto const* const nextOp = peek_if()) { - isNextOpeningParens = openingParens == *nextOp; + isNextOpeningParens = (openingParens == *nextOp); } // There can be no `(` directly after function-args, thus do not perform any reduction if such a token is found. From 4dcdcae1ef1ee45904cd9422265a4a35f8020f28 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 15:47:41 +0200 Subject: [PATCH 104/130] chore: slightly simplify parsing::NameParser::unwrap_msvc_like_function --- include/mimic++/printing/type/NameParser.hpp | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index e512ebb34..2e14048ea 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -496,8 +496,8 @@ namespace mimicpp::printing::type::parsing auto funIdentifier = std::get(m_TokenStack.back()); m_TokenStack.pop_back(); - token::ScopeSequence scopes{}; - if (auto* scopeSeq = match_suffix(m_TokenStack)) + std::optional scopes{}; + if (auto* const scopeSeq = match_suffix(m_TokenStack)) { scopes = std::move(*scopeSeq); m_TokenStack.pop_back(); @@ -526,17 +526,10 @@ namespace mimicpp::printing::type::parsing m_TokenStack.pop_back(); } - if (auto* targetScopes = match_suffix(m_TokenStack)) + MIMICPP_ASSERT(!is_suffix_of(m_TokenStack), "Invlid state."); + if (scopes) { - auto& target = targetScopes->scopes; - target.insert( - target.cend(), - std::make_move_iterator(scopes.scopes.begin()), - std::make_move_iterator(scopes.scopes.end())); - } - else - { - m_TokenStack.emplace_back(std::move(scopes)); + m_TokenStack.emplace_back(*std::move(scopes)); } m_TokenStack.emplace_back(std::move(funIdentifier)); From 9224b1b42ed521b763128ff48170779e02c94d1b Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Tue, 6 May 2025 16:28:28 +0200 Subject: [PATCH 105/130] fix: parsing::NameParser handles msvc's std::stacktrace special characteristics --- include/mimic++/printing/type/NameParser.hpp | 22 ++++++++++++++++++++ test/unit-tests/printing/TypeNameParser.cpp | 21 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index 2e14048ea..bde95e4da 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -91,6 +91,8 @@ namespace mimicpp::printing::type::parsing static constexpr lexing::operator_or_punctuator colon{":"}; static constexpr lexing::operator_or_punctuator leftShift{"<<"}; static constexpr lexing::operator_or_punctuator rightShift{">>"}; + static constexpr lexing::operator_or_punctuator plus{"+"}; + static constexpr lexing::operator_or_punctuator exclamationMark{"!"}; static constexpr lexing::keyword operatorKeyword{"operator"}; static constexpr lexing::keyword constKeyword{"const"}; static constexpr lexing::keyword volatileKeyword{"volatile"}; @@ -487,6 +489,26 @@ namespace mimicpp::printing::type::parsing 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(); + } } void unwrap_msvc_like_function() diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 971e80750..838fac506 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -2027,6 +2027,27 @@ TEST_CASE( } } +TEST_CASE( + "parsing::NameParser handles msvc's std::stacktrace special characteristics.", + "[print][print::type]") +{ + StringT const identifier = "executable!foo+0x1337"; + + VisitorMock visitor{}; + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), identifier}; + parser.parse_type(); +} + TEST_CASE( "parsing::NameParser keeps meaningful reserved identifiers.", "[print][print::type]") From d917bc533cab9a5e7d5017d47e9f851bc2ad3c15 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 17:01:01 +0200 Subject: [PATCH 106/130] cleanup: remove obsolete type-name post-processing symbols --- .../mimic++/printing/state/CommonTypes.hpp | 2 +- .../mimic++/printing/type/PostProcessing.hpp | 821 --------- include/mimic++/printing/type/PrintType.hpp | 137 +- test/unit-tests/printing/CMakeLists.txt | 1 - .../unit-tests/printing/TypeScopeIterator.cpp | 1480 ----------------- 5 files changed, 131 insertions(+), 2310 deletions(-) delete mode 100644 include/mimic++/printing/type/PostProcessing.hpp delete mode 100644 test/unit-tests/printing/TypeScopeIterator.cpp diff --git a/include/mimic++/printing/state/CommonTypes.hpp b/include/mimic++/printing/state/CommonTypes.hpp index 0ef7f933a..471a792c3 100644 --- a/include/mimic++/printing/state/CommonTypes.hpp +++ b/include/mimic++/printing/state/CommonTypes.hpp @@ -15,7 +15,7 @@ #include "mimic++/printing/Fwd.hpp" #include "mimic++/printing/PathPrinter.hpp" #include "mimic++/printing/state/Print.hpp" -#include "mimic++/printing/type/PostProcessing.hpp" +#include "mimic++/printing/type/PrintType.hpp" #include "mimic++/utilities/C++23Backports.hpp" // unreachable #include diff --git a/include/mimic++/printing/type/PostProcessing.hpp b/include/mimic++/printing/type/PostProcessing.hpp deleted file mode 100644 index 444b76260..000000000 --- a/include/mimic++/printing/type/PostProcessing.hpp +++ /dev/null @@ -1,821 +0,0 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 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 MIMICPP_PRINTING_TYPE_POST_PROCESSING_HPP -#define MIMICPP_PRINTING_TYPE_POST_PROCESSING_HPP - -#pragma once - -#include "mimic++/Fwd.hpp" -#include "mimic++/config/Config.hpp" -#include "mimic++/printing/Format.hpp" -#include "mimic++/printing/type/NameParser.hpp" -#include "mimic++/printing/type/NamePrintVisitor.hpp" -#include "mimic++/utilities/Algorithm.hpp" - -#include -#include -#include -#include -#include - -namespace mimicpp::printing::type -{ - /** - * \brief Prettifies a demangled name. - * \ingroup PRINTING_TYPE - * \tparam OutIter The print-iterator type. - * \param out The print iterator. - * \param name The demangled name to be prettified. - * \return The current print iterator. - * - * \details This function formats a type or template name for better readability. - * The primary strategy is to minimize unnecessary details while retaining essential information. - * Although this may introduce some ambiguity, it is generally more beneficial to provide an approximate name. - * - * For example, when a template-dependent type is provided, the template arguments are omitted: - * `std::vector::iterator` => `std::vector::iterator` - * - * \attention Providing a mangled name will result in unexpected behavior. - * \note When `MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES` is disabled, - * this function simply outputs the provided name without any modifications. - */ - template - constexpr OutIter prettify_type(OutIter out, StringT name); -} - -#ifndef MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES - -namespace mimicpp::printing::type -{ - template - constexpr OutIter prettify_type(OutIter out, StringT name) - { - out = std::ranges::copy(name, std::move(out)).out; - - return out; - } - - template - constexpr OutIter prettify_function(OutIter out, StringT name) - { - out = std::ranges::copy(name, std::move(out)).out; - - return out; - } -} - -#else - - #include - -namespace mimicpp -{ - using RegexT = std::regex; - using SMatchT = std::smatch; - using SVMatchT = std::match_results; -} - -namespace mimicpp::printing::type::detail -{ - constexpr StringViewT anonymousNamespaceTargetScopeText{"{anon-ns}"}; - constexpr StringViewT scopeDelimiter{"::"}; - constexpr StringViewT argumentDelimiter{","}; - - #if MIMICPP_DETAIL_IS_MSVC \ - || MIMICPP_DETAIL_IS_CLANG_CL - constexpr std::array openingBrackets{'<', '(', '[', '{', '`'}; - constexpr std::array closingBrackets{'>', ')', ']', '}', '\''}; - #else - constexpr std::array openingBrackets{'<', '(', '[', '{'}; - constexpr std::array closingBrackets{'>', ')', ']', '}'}; - #endif - - // see: https://en.cppreference.com/w/cpp/string/byte/isspace - constexpr auto is_space = [](char const c) noexcept { - return static_cast(std::isspace(static_cast(c))); - }; - - // see: https://en.cppreference.com/w/cpp/string/byte/isdigit - constexpr auto is_digit = [](char const c) noexcept { - return static_cast(std::isdigit(static_cast(c))); - }; - - // see: https://en.cppreference.com/w/cpp/string/byte/isxdigit - constexpr auto is_hex_digit = [](char const c) noexcept { - return static_cast(std::isxdigit(static_cast(c))); - }; - - template - requires std::constructible_from - [[nodiscard]] - constexpr StringViewT trimmed(Iter const begin, Iter const end) - { - auto const trimmedBegin = std::ranges::find_if_not(begin, end, is_space); - auto const trimmedEnd = std::ranges::find_if_not( - std::make_reverse_iterator(end), - std::make_reverse_iterator(trimmedBegin), - is_space); - return StringViewT{trimmedBegin, trimmedEnd.base()}; - } - - [[nodiscard]] - constexpr StringViewT trimmed(StringViewT const str) - { - return trimmed(str.cbegin(), str.cend()); - } - - struct function_info - { - StringViewT returnType{}; - StringViewT argList{}; - StringViewT specs{}; - }; - - struct template_info - { - StringViewT argList{}; - StringViewT specs{}; - }; - - struct scope_info - { - StringViewT identifier{}; - std::optional functionInfo{}; - std::optional templateInfo{}; - }; - - [[nodiscard]] - constexpr std::ranges::borrowed_subrange_t detect_last_enclosed( - StringViewT const& name, - StringViewT const opening, - StringViewT const closing) - { - MIMICPP_ASSERT(!name.starts_with(' ') && !name.ends_with(' '), "Name is not trimmed."); - MIMICPP_ASSERT(1u == opening.size(), "Opening token must have a size of one."); - MIMICPP_ASSERT(1u == closing.size(), "Closing token must have a size of one."); - - auto reversedName = name | std::views::reverse; - - auto const delimiterMatch = util::find_next_unwrapped_token( - reversedName, - scopeDelimiter, - closingBrackets, - openingBrackets); - auto const closingMatch = util::find_next_unwrapped_token( - std::ranges::subrange{reversedName.begin(), delimiterMatch.begin()}, - closing, - closingBrackets, - openingBrackets); - auto const openingIter = util::find_closing_token( - std::ranges::subrange{closingMatch.end(), delimiterMatch.begin()}, - closing.front(), - opening.front()); - // If no `opening closing` range could be found or the identifier between `scopeDelimiter opening` is empty, - // it's not what we are looking for. - if (openingIter == delimiterMatch.begin() - || std::ranges::empty(trimmed(delimiterMatch.begin().base(), openingIter.base() - 1))) - { - return {name.cend(), name.cend()}; - } - - return {openingIter.base() - 1, closingMatch.begin().base()}; - } - - [[nodiscard]] - constexpr std::optional detect_function_scope_info(StringViewT& name) - { - MIMICPP_ASSERT(!name.starts_with(' ') && !name.ends_with(' '), "Name is not trimmed."); - - auto const enclosedMatch = detect_last_enclosed(name, "(", ")"); - if (enclosedMatch.empty()) - { - return std::nullopt; - } - MIMICPP_ASSERT(enclosedMatch.front() == '(' && enclosedMatch.back() == ')', "Unexpected result."); - - function_info info{ - .argList = trimmed(enclosedMatch.begin() + 1, enclosedMatch.end() - 1), - .specs = trimmed(enclosedMatch.end(), name.end())}; - if (info.argList == "void") - { - info.argList = {}; - } - - // Do not trim here, as this will otherwise lead to indistinguishable symbols. - // Consider the function-type `r()`; Compilers will generate the name `r ()` for this (note the additional ws). - // Due to reasons, some compilers will also generate `i()` (and thus omit the return-type) - // for functions with complex return types. The delimiting ws is the only assumption we have. - // Due to this, an empty identifier is valid. - name = {name.cbegin(), enclosedMatch.begin()}; - - // If a whitespace exists before `(`, it's probably the return type. - if (auto const returnTypeDelimiter = util::find_next_unwrapped_token( - name | std::views::reverse, - " ", - closingBrackets, - openingBrackets)) - { - info.returnType = trimmed(name.cbegin(), returnTypeDelimiter.end().base()); - name = {returnTypeDelimiter.begin().base(), name.cend()}; - } - - return info; - } - - [[nodiscard]] - constexpr std::optional detect_template_scope_info(StringViewT& name) - { - MIMICPP_ASSERT(!name.starts_with(' ') && !name.ends_with(' '), "Name is not trimmed."); - - auto const enclosedMatch = detect_last_enclosed(name, "<", ">"); - if (enclosedMatch.empty()) - { - return std::nullopt; - } - MIMICPP_ASSERT(enclosedMatch.front() == '<' && enclosedMatch.back() == '>', "Unexpected result."); - - StringViewT const specs = trimmed(enclosedMatch.end(), name.cend()); - StringViewT const args = trimmed(enclosedMatch.begin() + 1, enclosedMatch.end() - 1); - name = trimmed(name.cbegin(), enclosedMatch.begin()); - - return template_info{ - .argList = args, - .specs = specs}; - } - - [[nodiscard]] - constexpr scope_info gather_scope_info(StringViewT scope) - { - scope_info info{}; - info.functionInfo = detect_function_scope_info(scope); - info.templateInfo = detect_template_scope_info(scope); - - info.identifier = scope; - - return info; - } - - class ScopeIterator - { - public: - [[nodiscard]] - explicit constexpr ScopeIterator(StringViewT content) noexcept - : m_Pending{std::move(content)} - { - } - - [[nodiscard]] - constexpr StringViewT pending() const noexcept - { - return m_Pending; - } - - [[nodiscard]] - std::optional operator()() - { - if (m_Pending.empty()) - { - return std::nullopt; - } - - auto const delimiter = util::find_next_unwrapped_token( - m_Pending, - scopeDelimiter, - openingBrackets, - closingBrackets); - StringViewT const scope = trimmed(m_Pending.cbegin(), delimiter.begin()); - m_Pending = StringViewT{delimiter.end(), m_Pending.end()}; - - return gather_scope_info(scope); - } - - private: - StringViewT m_Pending; - }; - - [[nodiscard]] - inline StringT transform_special_operators(StringT name) - { - static RegexT const transformOpSpaceship{R"(\boperator\s?<=>)"}; - name = std::regex_replace(name, transformOpSpaceship, "operator-spaceship"); - - static RegexT const transformOpLessEq{R"(\boperator\s?<=)"}; - name = std::regex_replace(name, transformOpLessEq, "operator-le"); - - static RegexT const transformOpLess{R"(\boperator\s?<)"}; - name = std::regex_replace(name, transformOpLess, "operator-lt"); - - static RegexT const transformOpGreaterEq{R"(\boperator\s?>=)"}; - name = std::regex_replace(name, transformOpGreaterEq, "operator-ge"); - - static RegexT const transformOpGreater{R"(\boperator\s?>)"}; - name = std::regex_replace(name, transformOpGreater, "operator-gt"); - - static RegexT const transformOpInvoke{R"(\boperator\s?\(\))"}; - name = std::regex_replace(name, transformOpInvoke, "operator-invoke"); - - return name; - } - - [[nodiscard]] - constexpr StringT remove_template_details(StringT name) - { - if (name.ends_with(']')) - { - auto rest = name | std::views::reverse | std::views::drop(1); - if (auto const closingIter = util::find_closing_token(rest, ']', '['); - closingIter != rest.end()) - { - auto const end = std::ranges::find_if_not(closingIter + 1, rest.end(), is_space); - name.erase(end.base(), name.end()); - } - } - - return name; - } -} - - #if MIMICPP_DETAIL_IS_GCC \ - || MIMICPP_DETAIL_IS_CLANG -namespace mimicpp::printing::type::detail -{ - #if MIMICPP_DETAIL_USES_LIBCXX - - constexpr StringT unify_lambdas(StringT name) - { - // `'lambda'(...)` => `lambda` - // or `'lambdaid'(...) => 'lambdaid' - - constexpr StringViewT lambdaPrefix{"\'lambda"}; - - auto first = name.begin(); - while (auto const match = std::ranges::search( - first, - name.end(), - lambdaPrefix.cbegin(), - lambdaPrefix.cend())) - { - // These lambdas sometimes have and sometimes have not an id. - auto const newEnd = std::shift_left( - match.begin(), - std::ranges::find_if_not(match.end(), name.cend(), is_digit), - 1); - - auto const parensBegin = std::ranges::find(newEnd, name.cend(), '('); - MIMICPP_ASSERT(parensBegin != name.cend(), "No begin-parenthesis found."); - auto const parensEnd = util::find_closing_token( - std::ranges::subrange{parensBegin + 1, name.cend()}, - '(', - ')'); - MIMICPP_ASSERT(parensEnd != name.cend(), "No end-parenthesis found."); - - name.erase(newEnd, parensEnd + 1); - - first = newEnd; - } - - return name; - } - - #else - - constexpr StringT unify_lambdas_type1(StringT name) - { - // `{lambda(...)#id}` => `lambda#id` - - constexpr StringViewT lambdaPrefix{"{lambda("}; - - auto first = name.begin(); - while (auto const match = std::ranges::search( - first, - name.end(), - lambdaPrefix.cbegin(), - lambdaPrefix.cend())) - { - std::ranges::subrange const rest{match.end(), name.cend()}; - auto const braceEnd = util::find_closing_token(rest, '{', '}'); - MIMICPP_ASSERT(braceEnd != name.cend(), "No corresponding end found."); - - auto const idToken = util::find_next_unwrapped_token( - std::ranges::subrange{match.end() - 1, braceEnd}, - StringViewT{"#"}, - openingBrackets, - closingBrackets); - - // Bring `lambda#id`to the front. - auto newEnd = std::shift_left(match.begin(), match.end() - 1, 1); - newEnd = std::ranges::copy(idToken.begin(), braceEnd, newEnd).out; - - name.erase(newEnd, braceEnd + 1); - - first = newEnd; - } - - return name; - } - - constexpr StringT unify_lambdas_type2(StringT name) - { - // `` => `lambda` - - constexpr StringViewT lambdaPrefix{"'); - MIMICPP_ASSERT(angleEnd != name.cend(), "No corresponding end found."); - - auto const newEnd = std::shift_left(match.begin(), match.end() - 1, 1); - - name.erase(newEnd, angleEnd + 1); - - first = newEnd; - } - - return name; - } - - constexpr StringT unify_lambdas(StringT name) - { - name = unify_lambdas_type1(std::move(name)); - name = unify_lambdas_type2(std::move(name)); - - return name; - } - - #endif - - [[nodiscard]] - inline StringT apply_basic_transformations(StringT name) - { - name = remove_template_details(std::move(name)); - - if (constexpr StringViewT constexprToken{"constexpr "}; - name.starts_with(constexprToken)) - { - name.erase(0, constexprToken.size()); - } - - static const RegexT unifyStdImplNamespace{ - #if MIMICPP_DETAIL_USES_LIBCXX - "std::__1::" - #else - "std::__cxx11::" - #endif - }; - name = std::regex_replace(name, unifyStdImplNamespace, "std::"); - - name = transform_special_operators(std::move(name)); - name = unify_lambdas(std::move(name)); - - return name; - } -} - #else - -namespace mimicpp::printing::type::detail -{ - /** - * \brief Erases the arg list, return type, ``` and `'` pair, and all specifiers, but keeps the `()` as a marker for later steps. - */ - constexpr StringT::const_iterator simplify_special_function_scope(StringT& name, StringT::const_iterator const wrapBegin) - { - // `\`ret fn(...) specs'` => `fn(...) specs` - - auto const contentBegin = std::ranges::find_if_not(wrapBegin + 1, name.cend(), is_space); - auto const wrapEnd = util::find_closing_token( - std::ranges::subrange{contentBegin, name.cend()}, - '`', - '\''); - MIMICPP_ASSERT(wrapEnd != name.cend(), "No corresponding end found."); - - auto const parensEnd = util::find_next_unwrapped_token( - std::ranges::subrange{ - std::make_reverse_iterator(wrapEnd), - std::make_reverse_iterator(contentBegin)}, - ")", - closingBrackets, - openingBrackets); - auto const parensBegin = util::find_closing_token( - std::ranges::subrange{parensEnd.end(), std::make_reverse_iterator(contentBegin)}, - ')', - '('); - if (!parensEnd - || parensBegin.base() == contentBegin) - { - return wrapEnd + 1; - } - - // The return type seems optional. So, if not delimiter can be found it may still be a function. - auto const delimiter = util::find_next_unwrapped_token( - std::ranges::subrange{parensBegin + 1, std::make_reverse_iterator(contentBegin)}, - " ", - closingBrackets, - openingBrackets); - auto const end = std::ranges::copy( - delimiter.begin().base(), - wrapEnd, - name.begin() + std::ranges::distance(name.cbegin(), wrapBegin)) - .out; - - name.erase(end, wrapEnd + 1); - - // return the current position, as we may have copied another special scope to the beginning - return wrapBegin; - } - - constexpr StringT simplify_special_functions(StringT name) - { - for (auto iter = std::ranges::find(std::as_const(name), '`'); - iter != name.cend(); - iter = std::ranges::find(iter, name.cend(), '`')) - { - iter = simplify_special_function_scope(name, iter); - } - - return name; - } - - #if MIMICPP_DETAIL_IS_32BIT - - [[nodiscard]] - constexpr StringT remove_prefix_scope(StringT name) - { - MIMICPP_ASSERT(!name.starts_with(' ') && !name.ends_with(' '), "Name is not trimmed."); - - // msvc with c++23 and Win32 seems to prefix some symbols with prefix, followed by a single `!`. - // E.g. `mimicpp_tests!` - - if (auto const iter = std::ranges::find(name, '!'); - iter != name.cend()) - { - // Make sure it's not `operator!` - if (!trimmed(name.begin(), iter).ends_with("operator")) - { - name.erase(name.cbegin(), iter + 1); - } - } - - return name; - } - - [[nodiscard]] - constexpr StringT remove_function_suffix(StringT name) - { - MIMICPP_ASSERT(!name.starts_with(' ') && !name.ends_with(' '), "Name is not trimmed."); - - // msvc with c++23 and Win32 seems to suffix top-level functions with `+0x0123` - - auto reversedName = name | std::views::reverse; - if (auto const iter = std::ranges::find_if_not(reversedName, is_hex_digit); - iter != reversedName.begin()) - { - constexpr StringViewT token{"+0x"}; - if (StringViewT const rest{name.cbegin(), iter.base()}; - rest.ends_with(token)) - { - name.erase( - iter.base() - std::ranges::ssize(token), - name.cend()); - } - } - - return name; - } - - #endif - - [[nodiscard]] - inline StringT apply_basic_transformations(StringT name) - { - name = remove_template_details(std::move(name)); - - #if MIMICPP_DETAIL_IS_32BIT - name = remove_prefix_scope(std::move(name)); - name = remove_function_suffix(std::move(name)); - #endif - - name = transform_special_operators(std::move(name)); - - static RegexT const omitClassStructEnum{R"(\b(class|struct|enum)\s+)"}; - name = std::regex_replace(name, omitClassStructEnum, ""); - - static RegexT const omitAccessSpecifiers{R"(\b(?:public|private|protected):\s+)"}; - name = std::regex_replace(name, omitAccessSpecifiers, ""); - - static const RegexT omitVirtualNamespace{R"(`\d+'::)"}; - name = std::regex_replace(name, omitVirtualNamespace, ""); - - // something like call-convention and __ptr64 - static RegexT const omitImplementationSpecifiers{R"(\b__\w+\b\s*)"}; - name = std::regex_replace(name, omitImplementationSpecifiers, ""); - - static const RegexT prettifyLambda{R"()"}; - name = std::regex_replace(name, prettifyLambda, "lambda#$1"); - - name = simplify_special_functions(std::move(name)); - - return name; - } -} - - #endif - -namespace mimicpp::printing::type::detail -{ - template - constexpr OutIter prettify(OutIter out, StringViewT name); - - template - constexpr OutIter prettify_function_identifier(OutIter out, [[maybe_unused]] StringViewT const scope, StringViewT identifier) - { - // When `operator` is given, then actually `operator()` were used. - if (identifier == "operator") - { - identifier = "operator()"; - } - - return format::format_to(std::move(out), "{{{}}}::", identifier); - } - - template - constexpr OutIter prettify_arg_list(OutIter out, StringViewT const argList) - { - bool isFirst{true}; - StringViewT pendingArgList{argList}; - while (!pendingArgList.empty()) - { - if (!std::exchange(isFirst, false)) - { - out = format::format_to(std::move(out), ", "); - } - - auto const tokenDelimiter = util::find_next_unwrapped_token( - pendingArgList, - argumentDelimiter, - openingBrackets, - closingBrackets); - out = detail::prettify( - std::move(out), - trimmed(pendingArgList.begin(), tokenDelimiter.begin())); - pendingArgList = StringViewT{tokenDelimiter.end(), pendingArgList.end()}; - } - - return out; - } - - template - constexpr OutIter prettify_specs(OutIter out, StringViewT const specs) - { - out = std::ranges::copy(specs, std::move(out)).out; - - return out; - } - - [[nodiscard]] - inline auto& alias_map() - { - static std::unordered_map const aliases{ - {"(anonymous namespace)", anonymousNamespaceTargetScopeText}, - { "{anonymous}", anonymousNamespaceTargetScopeText}, - {"`anonymous namespace'", anonymousNamespaceTargetScopeText}, - { "", "lambda"} - }; - - return aliases; - } - - template - constexpr OutIter handle_scope(OutIter out, scope_info const& scope) - { - auto const& aliases = alias_map(); - auto const& identifier = scope.identifier; - - // detect member function pointer. - if (identifier.ends_with("::*)") - && identifier.starts_with("(")) - { - out = format::format_to(std::move(out), "("); - out = detail::prettify( - std::move(out), - StringViewT{identifier.cbegin() + 1, identifier.cend() - 1}); - out = format::format_to(std::move(out), ")"); - } - #if MIMICPP_DETAIL_IS_MSVC - else if (identifier.starts_with('`') && identifier.ends_with('\'')) - { - out = detail::prettify( - std::move(out), - StringViewT{identifier.cbegin() + 1, identifier.cend() - 1}); - } - #endif - else if (auto const iter = aliases.find(scope.identifier); - iter != aliases.cend()) - { - out = std::ranges::copy(iter->second, std::move(out)).out; - } - else - { - out = std::ranges::copy(scope.identifier, std::move(out)).out; - } - - return out; - } - - template - constexpr OutIter handle_identifier(OutIter out, StringViewT const name) - { - ScopeIterator iter{name}; - bool isFirst{true}; - while (auto const scopeInfo = iter()) - { - if (!std::exchange(isFirst, false)) - { - out = std::ranges::copy(scopeDelimiter, std::move(out)).out; - } - - out = handle_scope(std::move(out), *scopeInfo); - } - - return out; - } - - template - constexpr OutIter prettify(OutIter out, StringViewT const name) - { - auto&& [identifier, functionInfo, templateInfo] = gather_scope_info(name); - - if (functionInfo - && !functionInfo->returnType.empty()) - { - out = detail::prettify(std::move(out), functionInfo->returnType); - out = format::format_to(std::move(out), " "); - } - - out = detail::handle_identifier(std::move(out), identifier); - - if (templateInfo) - { - auto const& [args, specs] = *templateInfo; - - out = format::format_to(std::move(out), "<"); - out = detail::prettify_arg_list(std::move(out), args); - out = format::format_to(std::move(out), ">"); - out = prettify_specs(std::move(out), specs); - } - - if (functionInfo) - { - auto const& [ret, args, specs] = *functionInfo; - - out = format::format_to(std::move(out), "("); - out = detail::prettify_arg_list(std::move(out), args); - out = format::format_to(std::move(out), ")"); - out = prettify_specs(std::move(out), specs); - } - - return out; - } -} - -namespace mimicpp::printing::type -{ - template - constexpr OutIter prettify_type(OutIter out, StringT name) - { - static_assert(parsing::parser_visitor>); - - PrintVisitor visitor{std::move(out)}; - parsing::NameParser parser{std::ref(visitor), name}; - parser.parse_type(); - - return visitor.out(); - } - - template - constexpr OutIter prettify_function(OutIter out, StringT name) - { - name = detail::remove_template_details(std::move(name)); - - static_assert(parsing::parser_visitor>); - - PrintVisitor visitor{std::move(out)}; - parsing::NameParser parser{std::ref(visitor), name}; - parser.parse_function(); - - return visitor.out(); - } -} - -#endif - -#endif diff --git a/include/mimic++/printing/type/PrintType.hpp b/include/mimic++/printing/type/PrintType.hpp index df3d287c7..25ca359a8 100644 --- a/include/mimic++/printing/type/PrintType.hpp +++ b/include/mimic++/printing/type/PrintType.hpp @@ -11,11 +11,9 @@ #include "mimic++/Fwd.hpp" #include "mimic++/printing/Format.hpp" #include "mimic++/printing/Fwd.hpp" -#include "mimic++/printing/type/PostProcessing.hpp" #include "mimic++/utilities/PriorityTag.hpp" #include -#include #include #include #include @@ -36,15 +34,60 @@ namespace mimicpp::printing::type * When `MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES` is enabled, it further demangles the name using `abi::__cxa_demangle`. */ template + [[nodiscard]] StringT type_name(); + + /** + * \brief Prettifies a demangled name. + * \ingroup PRINTING_TYPE + * \tparam OutIter The print-iterator type. + * \param out The print iterator. + * \param name The demangled name to be prettified. + * \return The current print iterator. + * + * \details This function formats a type or template name for better readability. + * The primary strategy is to minimize unnecessary details while retaining essential information. + * Although this may introduce some ambiguity, it is generally more beneficial to provide an approximate name. + * + * For example, when a template-dependent type is provided, the template arguments are omitted: + * `std::vector::iterator` => `std::vector::iterator` + * + * \attention Providing a mangled name will result in unexpected behavior. + * \note When `MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES` is disabled, + * this function simply outputs the provided name without any modifications. + */ + template + constexpr OutIter prettify_type(OutIter out, StringT name); + + /** + * \brief Prettifies a function name produces by e.g. `std::source_location::function_name()`. + * \ingroup PRINTING_TYPE + * \tparam OutIter The print-iterator type. + * \param out The print iterator. + * \param name The function name to be prettified. + * \return The current print iterator. + * + * \details This function formats a function for better readability. + * The primary strategy is to minimize unnecessary details while retaining essential information. + * Although this may introduce some ambiguity, it is generally more beneficial to provide an approximate name. + * + * For example, when a template-dependent type is provided, the template arguments are omitted: + * `void fun(std::vector::iterator)` => `void fun(std::vector::iterator)` + * + * \note When `MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES` is disabled, + * this function simply outputs the provided name without any modifications. + */ + template + constexpr OutIter prettify_function(OutIter out, StringT name); } -#if defined(MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES) \ - && (MIMICPP_DETAIL_IS_GCC || MIMICPP_DETAIL_IS_CLANG) +#ifdef MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES - #include - #include - #include + #if MIMICPP_DETAIL_IS_GCC || MIMICPP_DETAIL_IS_CLANG + + #include + #include + #include namespace mimicpp::printing::type { @@ -67,6 +110,74 @@ namespace mimicpp::printing::type } } + #else + +namespace mimicpp::printing::type +{ + template + StringT type_name() + { + return typeid(T).name(); + } +} + + #endif + + #include "mimic++/printing/type/NameParser.hpp" + #include "mimic++/printing/type/NamePrintVisitor.hpp" + +namespace mimicpp::printing::type +{ + template + constexpr OutIter prettify_type(OutIter out, StringT name) + { + static_assert(parsing::parser_visitor>); + + PrintVisitor visitor{std::move(out)}; + parsing::NameParser parser{std::ref(visitor), name}; + parser.parse_type(); + + return visitor.out(); + } + + namespace detail + { + [[nodiscard]] + constexpr StringT remove_template_details(StringT name) + { + if (name.ends_with(']')) + { + auto rest = name | std::views::reverse | std::views::drop(1); + if (auto const closingIter = util::find_closing_token(rest, ']', '['); + closingIter != rest.end()) + { + auto const end = std::ranges::find_if_not( + closingIter + 1, + rest.end(), + lexing::is_space); + name.erase(end.base(), name.end()); + } + } + + return name; + } + } + + template + constexpr OutIter prettify_function(OutIter out, StringT name) + { + name = detail::remove_template_details(std::move(name)); + + static_assert(parsing::parser_visitor>); + + PrintVisitor visitor{std::move(out)}; + parsing::NameParser parser{std::ref(visitor), name}; + parser.parse_function(); + + return visitor.out(); + } +} + #else namespace mimicpp::printing::type @@ -76,6 +187,18 @@ namespace mimicpp::printing::type { return typeid(T).name(); } + + template + constexpr OutIter prettify_type(OutIter out, StringT name) + { + return std::ranges::copy(name, std::move(out)).out; + } + + template + constexpr OutIter prettify_function(OutIter out, StringT name) + { + return std::ranges::copy(name, std::move(out)).out; + } } #endif diff --git a/test/unit-tests/printing/CMakeLists.txt b/test/unit-tests/printing/CMakeLists.txt index a08cac0e5..7d78aa93e 100644 --- a/test/unit-tests/printing/CMakeLists.txt +++ b/test/unit-tests/printing/CMakeLists.txt @@ -10,7 +10,6 @@ target_sources(${TARGET_NAME} "PathPrinter.cpp" "StatePrinter.cpp" "FunctionTypePostProcessing.cpp" - "TypeScopeIterator.cpp" "TypePostProcessing.cpp" "TypePrinter.cpp" "TypeNameLexer.cpp" diff --git a/test/unit-tests/printing/TypeScopeIterator.cpp b/test/unit-tests/printing/TypeScopeIterator.cpp deleted file mode 100644 index 42e12c558..000000000 --- a/test/unit-tests/printing/TypeScopeIterator.cpp +++ /dev/null @@ -1,1480 +0,0 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 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 "mimic++/printing/TypePrinter.hpp" - -#include - -#ifdef MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES - -using namespace mimicpp; - -namespace -{ - template - [[nodiscard]] - StringT name_of() - { - return printing::type::detail::apply_basic_transformations( - printing::type::type_name()); - } -} - -struct TypeScopeVisitor_cpp_my_type -{ -}; - -namespace -{ - struct my_type - { - }; - - struct - { - - } constexpr anonStruct [[maybe_unused]]{}; - - class - { - - } constexpr anonClass [[maybe_unused]]{}; - - enum - { - - } constexpr anonEnum [[maybe_unused]]{}; -} - -namespace test_ns -{ - namespace - { - struct my_type - { - }; - } -} - -TEST_CASE( - "printing::type::detail::ScopeIterator advances the scopes of a given type-identifier.", - "[print][detail]") -{ - SECTION("When type in global namespace is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator visitor{rawName}; - - SECTION("First scope refers to the actual type name.") - { - auto const topLevelScope = visitor(); - CHECK(topLevelScope); - CHECK_FALSE(topLevelScope->functionInfo); - CHECK_FALSE(topLevelScope->templateInfo); - CHECK_THAT( - StringT{topLevelScope->identifier}, - Catch::Matchers::Equals("TypeScopeVisitor_cpp_my_type")); - - SECTION("No subsequent scopes exist.") - { - CHECK(std::nullopt == visitor()); - } - } - } - - SECTION("When type in anonymous-namespace is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator visitor{rawName}; - - SECTION("First scope refers to the anon-ns.") - { - auto const anonNsScope = visitor(); - CHECK(anonNsScope); - CHECK_FALSE(anonNsScope->functionInfo); - CHECK_FALSE(anonNsScope->templateInfo); - CHECK_THAT( - StringT{anonNsScope->identifier}, - Catch::Matchers::ContainsSubstring("anonymous") - && Catch::Matchers::ContainsSubstring("namespace")); - - SECTION("Second scope refers to the actual type name.") - { - auto const topLevelScope = visitor(); - CHECK(topLevelScope); - CHECK_FALSE(topLevelScope->functionInfo); - CHECK_FALSE(topLevelScope->templateInfo); - CHECK_THAT( - StringT{topLevelScope->identifier}, - Catch::Matchers::Equals("my_type")); - - SECTION("No subsequent scopes exist.") - { - CHECK(std::nullopt == visitor()); - } - } - } - } - - SECTION("When local-type is given.") - { - struct my_type - { - }; - - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator visitor{rawName}; - - SECTION("First scope refers the test-case.") - { - auto const anonNsScope = visitor(); - CHECK(anonNsScope); - CHECK_THAT( - StringT{anonNsScope->identifier}, - Catch::Matchers::Matches(R"(CATCH2_INTERNAL_TEST_\d+)")); - CHECK(anonNsScope->functionInfo); - // may or may not have a return type - CHECK_THAT( - anonNsScope->functionInfo->argList, - Catch::Matchers::IsEmpty()); - CHECK_THAT( - anonNsScope->functionInfo->specs, - Catch::Matchers::IsEmpty()); - CHECK_FALSE(anonNsScope->templateInfo); - - SECTION("Second scope refers to the actual type name.") - { - auto const topLevelScope = visitor(); - CHECK(topLevelScope); - CHECK_FALSE(topLevelScope->functionInfo); - CHECK_FALSE(topLevelScope->templateInfo); - CHECK_THAT( - StringT{topLevelScope->identifier}, - Catch::Matchers::Equals("my_type")); - - SECTION("No subsequent scopes exist.") - { - CHECK(std::nullopt == visitor()); - } - } - } - } - - SECTION("When type in arbitrarily nested namespace is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator visitor{rawName}; - - SECTION("First scope refers to the test-ns.") - { - auto const testNsScope = visitor(); - CHECK(testNsScope); - CHECK_FALSE(testNsScope->functionInfo); - CHECK_FALSE(testNsScope->templateInfo); - CHECK_THAT( - StringT{testNsScope->identifier}, - Catch::Matchers::Equals("test_ns")); - - SECTION("Second scope refers to the anon-ns.") - { - auto const anonNsScope = visitor(); - CHECK(anonNsScope); - CHECK_FALSE(anonNsScope->functionInfo); - CHECK_FALSE(anonNsScope->templateInfo); - CHECK_THAT( - StringT{anonNsScope->identifier}, - Catch::Matchers::ContainsSubstring("anonymous") - && Catch::Matchers::ContainsSubstring("namespace")); - - SECTION("Third scope refers to the actual type name.") - { - auto const topLevelScope = visitor(); - CHECK(topLevelScope); - CHECK_FALSE(topLevelScope->functionInfo); - CHECK_FALSE(topLevelScope->templateInfo); - CHECK_THAT( - StringT{topLevelScope->identifier}, - Catch::Matchers::Equals("my_type")); - - SECTION("No subsequent scopes exist.") - { - CHECK(std::nullopt == visitor()); - } - } - } - } - } - - SECTION("When anon-type is given.") - { - StringT const rawName = GENERATE( - name_of(), - name_of(), - name_of()); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator visitor{rawName}; - - REQUIRE(visitor()); - - auto const topLevelScope = visitor(); - CHECK(topLevelScope); - CHECK_FALSE(topLevelScope->functionInfo); - CHECK_FALSE(topLevelScope->templateInfo); - // The actual result is too different between all platforms. - // It's important that something is there; everything else would be too much of a hassle. - CHECK_THAT( - StringT{topLevelScope->identifier}, - !Catch::Matchers::IsEmpty()); - - REQUIRE_FALSE(visitor()); - } -} - -namespace -{ - [[maybe_unused]] constexpr auto anon_structLambda = [] { - struct - { - } constexpr obj [[maybe_unused]]{}; - - return obj; - }; - - [[maybe_unused]] constexpr auto anon_classLambda = [] { - struct - { - } constexpr obj [[maybe_unused]]{}; - - return obj; - }; - - [[maybe_unused]] constexpr auto anon_enumLambda = [] { - enum - { - } constexpr obj [[maybe_unused]]{}; - - return obj; - }; - - [[maybe_unused]] constexpr auto my_typeLambda = [] { - struct my_type - { - }; - - return my_type{}; - }; - - [[maybe_unused]] constexpr auto my_typeUnaryLambda = [](int) { - struct my_type - { - }; - - return my_type{}; - }; - - [[maybe_unused]] constexpr auto my_typeBinaryLambda = [](int, float) { - struct my_type - { - }; - - return my_type{}; - }; - - [[maybe_unused]] auto my_typeMutableLambda = []() mutable { - struct my_type - { - }; - - return my_type{}; - }; - - [[maybe_unused]] constexpr auto my_typeNoexceptLambda = []() noexcept { - struct my_type - { - }; - - return my_type{}; - }; - - [[maybe_unused]] constexpr auto my_typeNestedLambda = [] { - constexpr auto inner = [] { - struct my_type - { - }; - - return my_type{}; - }; - - return inner(); - }; - - [[maybe_unused]] constexpr auto my_typeComplexNestedLambda = [] { - [[maybe_unused]] constexpr auto dummy = [] {}; - [[maybe_unused]] constexpr auto dummy2 = [] {}; - - constexpr auto inner = [](int, float) { - struct my_type - { - }; - - return my_type{}; - }; - - [[maybe_unused]] constexpr auto dummy3 = [] {}; - [[maybe_unused]] constexpr auto dummy4 = [] {}; - - return inner(42, 4.2f); - }; - - void checkNamedScope( - printing::type::detail::ScopeIterator& iter, - auto const& nameMatcher) - { - CAPTURE(iter.pending()); - - auto const scope = iter(); - REQUIRE(scope); - REQUIRE_FALSE(scope->templateInfo); - REQUIRE_FALSE(scope->functionInfo); - - CHECK_THAT( - StringT{scope->identifier}, - nameMatcher); - } - - template < - typename ReturnTypeMatcher = Catch::Matchers::IsEmptyMatcher, - typename ArgListMatcher = Catch::Matchers::IsEmptyMatcher, - typename SpecsMatcher = Catch::Matchers::IsEmptyMatcher> - struct function_info_matchers - { - ReturnTypeMatcher returnTypeMatcher{}; - ArgListMatcher argListMatcher{}; - SpecsMatcher specsMatcher{}; - }; - - template < - typename ArgListMatcher = Catch::Matchers::IsEmptyMatcher, - typename SpecsMatcher = Catch::Matchers::IsEmptyMatcher> - struct template_info_matchers - { - ArgListMatcher argListMatcher{}; - SpecsMatcher specsMatcher{}; - }; - - void checkFunctionScope( - printing::type::detail::ScopeIterator& iter, - auto const& nameMatcher, - auto const& infoMatchers = template_info_matchers{}) - { - CAPTURE(iter.pending()); - - auto const fnScope = iter(); - REQUIRE(fnScope); - - CHECK_FALSE(fnScope->templateInfo); - CHECK_THAT( - StringT{fnScope->identifier}, - nameMatcher); - - REQUIRE(fnScope->functionInfo); - CHECK_THAT( - StringT{fnScope->functionInfo->returnType}, - infoMatchers.returnTypeMatcher); - CHECK_THAT( - StringT{fnScope->functionInfo->argList}, - infoMatchers.argListMatcher); - CHECK_THAT( - StringT{fnScope->functionInfo->specs}, - infoMatchers.specsMatcher); - } - - void checkTemplateFunctionScope( - printing::type::detail::ScopeIterator& iter, - auto const& nameMatcher, - auto const& templateInfoMatchers, - auto const& functionInfoMatchers) - { - CAPTURE(iter.pending()); - - auto const fnScope = iter(); - REQUIRE(fnScope); - - CHECK_THAT( - StringT{fnScope->identifier}, - nameMatcher); - - REQUIRE(fnScope->functionInfo); - CHECK_THAT( - StringT{fnScope->functionInfo->returnType}, - functionInfoMatchers.returnTypeMatcher); - CHECK_THAT( - StringT{fnScope->functionInfo->argList}, - functionInfoMatchers.argListMatcher); - CHECK_THAT( - StringT{fnScope->functionInfo->specs}, - functionInfoMatchers.specsMatcher); - - REQUIRE(fnScope->templateInfo); - CHECK_THAT( - StringT{fnScope->templateInfo->argList}, - templateInfoMatchers.argListMatcher); - CHECK_THAT( - StringT{fnScope->templateInfo->specs}, - templateInfoMatchers.specsMatcher); - } - - void checkLambdaScope(printing::type::detail::ScopeIterator& iter) - { - CAPTURE(iter.pending()); - - auto const info = iter(); - - REQUIRE(info); - CHECK_FALSE(info->templateInfo); - CHECK_FALSE(info->functionInfo); - - #if MIMICPP_DETAIL_IS_CLANG - - // clang often uses the `anon-type` form for lambdas - CHECKED_IF(info->identifier.starts_with('$')) - { - CHECK_THAT( - StringT{info->identifier}, - Catch::Matchers::Matches(R"(\$_\d+)")); - } - - // but sometimes uses actual `lambda` form - CHECKED_IF(info->identifier.starts_with("lambda")) - { - #if MIMICPP_DETAIL_USES_LIBCXX - CHECK_THAT( - StringT{info->identifier}, - Catch::Matchers::Equals("lambda")); - #else - CHECK_THAT( - StringT{info->identifier}, - Catch::Matchers::Matches(R"(lambda#\d+)")); - #endif - } - #elif MIMICPP_DETAIL_IS_GCC - CHECK_THAT( - StringT{info->identifier}, - Catch::Matchers::Matches(R"(lambda#\d+)")); - #endif - } - - void checkLambdaName( - [[maybe_unused]] StringT const& expectedName, - [[maybe_unused]] auto& iter) - { - CAPTURE(iter.pending()); - - #if MIMICPP_DETAIL_IS_GCC - auto const nameScope = iter(); - CHECK(nameScope); - CHECK_FALSE(nameScope->functionInfo); - CHECK_FALSE(nameScope->templateInfo); - CHECK_THAT( - StringT{nameScope->identifier}, - Catch::Matchers::Equals(expectedName)); - #endif - } - - void checkLambdaCallScope( - printing::type::detail::ScopeIterator& iter, - auto const& infoMatchers) - { - checkLambdaScope(iter); - checkFunctionScope(iter, Catch::Matchers::Equals("operator-invoke"), infoMatchers); - } -} - -TEST_CASE( - "printing::type::detail::ScopeIterator does not treat placeholders as templates or functions.", - "[print][detail]") -{ - StringT const placeholderName = GENERATE( - "", - "(placeholder)", - "`placeholder'"); - - SECTION("When a standalone placeholder is given.") - { - StringT const name = placeholderName; - - auto const scope = printing::type::detail::gather_scope_info(name); - REQUIRE_FALSE(scope.functionInfo); - REQUIRE_FALSE(scope.templateInfo); - - printing::type::detail::ScopeIterator iter{scope.identifier}; - checkNamedScope( - iter, - Catch::Matchers::Equals(placeholderName)); - REQUIRE_FALSE(iter()); - } - - SECTION("When top-level placeholder is given.") - { - StringT const name = "my_ns::" + placeholderName; - - auto const scope = printing::type::detail::gather_scope_info(name); - REQUIRE_FALSE(scope.functionInfo); - REQUIRE_FALSE(scope.templateInfo); - - printing::type::detail::ScopeIterator iter{scope.identifier}; - checkNamedScope( - iter, - Catch::Matchers::Equals("my_ns")); - checkNamedScope( - iter, - Catch::Matchers::Equals(placeholderName)); - REQUIRE_FALSE(iter()); - } - - SECTION("When an identifier contains a placeholder.") - { - StringT const name = "my_ns::" + placeholderName + "::my_type"; - - auto const scope = printing::type::detail::gather_scope_info(name); - REQUIRE_FALSE(scope.functionInfo); - REQUIRE_FALSE(scope.templateInfo); - - printing::type::detail::ScopeIterator iter{scope.identifier}; - checkNamedScope( - iter, - Catch::Matchers::Equals("my_ns")); - checkNamedScope( - iter, - Catch::Matchers::Equals(placeholderName)); - checkNamedScope( - iter, - Catch::Matchers::Equals("my_type")); - REQUIRE_FALSE(iter()); - } -} - -TEST_CASE( - "printing::type::detail::ScopeIterator supports operator().", - "[print][detail]") -{ - SECTION("When given standalone.") - { - StringT const name = printing::type::detail::apply_basic_transformations( - "return operator()() const&"); - - auto const scope = printing::type::detail::gather_scope_info(name); - REQUIRE_FALSE(scope.templateInfo); - REQUIRE(scope.functionInfo); - - CHECK_THAT( - StringT{scope.functionInfo->returnType}, - Catch::Matchers::Equals("return")); - CHECK_THAT( - scope.functionInfo->argList, - Catch::Matchers::IsEmpty()); - CHECK_THAT( - StringT{scope.functionInfo->specs}, - Catch::Matchers::Equals("const&")); - - printing::type::detail::ScopeIterator iter{scope.identifier}; - checkNamedScope( - iter, - Catch::Matchers::Equals("operator-invoke")); - REQUIRE_FALSE(iter()); - } - - SECTION("When given as top-level.") - { - StringT const name = printing::type::detail::apply_basic_transformations( - "return my_ns::operator()() const&"); - - auto const scope = printing::type::detail::gather_scope_info(name); - REQUIRE_FALSE(scope.templateInfo); - REQUIRE(scope.functionInfo); - - CHECK_THAT( - StringT{scope.functionInfo->returnType}, - Catch::Matchers::Equals("return")); - CHECK_THAT( - scope.functionInfo->argList, - Catch::Matchers::IsEmpty()); - CHECK_THAT( - StringT{scope.functionInfo->specs}, - Catch::Matchers::Equals("const&")); - - printing::type::detail::ScopeIterator iter{scope.identifier}; - checkNamedScope( - iter, - Catch::Matchers::Equals("my_ns")); - checkNamedScope( - iter, - Catch::Matchers::Equals("operator-invoke")); - REQUIRE_FALSE(iter()); - } - - SECTION("When an identifier contains a operator().") - { - StringT const name = printing::type::detail::apply_basic_transformations( - "my_ns::operator()() const&::my_type"); - - auto const scope = printing::type::detail::gather_scope_info(name); - REQUIRE_FALSE(scope.templateInfo); - REQUIRE_FALSE(scope.functionInfo); - - printing::type::detail::ScopeIterator iter{scope.identifier}; - checkNamedScope( - iter, - Catch::Matchers::Equals("my_ns")); - checkFunctionScope( - iter, - Catch::Matchers::Equals("operator-invoke"), - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .specsMatcher = Catch::Matchers::Equals("const&")}); - checkNamedScope( - iter, - Catch::Matchers::Equals("my_type")); - REQUIRE_FALSE(iter()); - } -} - -TEST_CASE( - "printing::type::detail::ScopeIterator supports all kinds of lambdas.", - "[print][detail]") -{ - SECTION("When immutable lambda is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName("my_typeLambda", iter); - checkLambdaScope(iter); - REQUIRE_FALSE(iter()); - } - - SECTION("When unary lambda is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName("my_typeUnaryLambda", iter); - checkLambdaScope(iter); - REQUIRE_FALSE(iter()); - } - - SECTION("When binary lambda is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName("my_typeBinaryLambda", iter); - checkLambdaScope(iter); - REQUIRE_FALSE(iter()); - } - - SECTION("When mutable lambda is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName("my_typeMutableLambda", iter); - checkLambdaScope(iter); - REQUIRE_FALSE(iter()); - } - - SECTION("When noexcept lambda is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName("my_typeNoexceptLambda", iter); - checkLambdaScope(iter); - REQUIRE_FALSE(iter()); - } -} - -TEST_CASE( - "printing::type::detail::ScopeIterator supports lambda-local types.", - "[print][detail]") -{ - constexpr auto checkTopLevel = [](auto& visitor) { - auto const topLevelScope = visitor(); - CHECK(topLevelScope); - CHECK_FALSE(topLevelScope->functionInfo); - CHECK_FALSE(topLevelScope->templateInfo); - CHECK_THAT( - StringT{topLevelScope->identifier}, - Catch::Matchers::Equals("my_type")); - }; - - SECTION("When anon lambda-local type is given.") - { - auto const [expectedLambdaName, rawName] = GENERATE( - (table)({ - { "anon_classLambda", name_of()}, - {"anon_structLambda", name_of()}, - { "anon_enumLambda", name_of()} - })); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName(expectedLambdaName, iter); - checkLambdaCallScope( - iter, - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .specsMatcher = Catch::Matchers::Equals("const")}); - - auto const topLevelScope = iter(); - CHECK(topLevelScope); - CHECK_FALSE(topLevelScope->functionInfo); - CHECK_FALSE(topLevelScope->templateInfo); - CHECK_THAT( - topLevelScope->identifier, - !Catch::Matchers::IsEmpty()); - - REQUIRE_FALSE(iter()); - } - - SECTION("When immutable lambda is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName("my_typeLambda", iter); - checkLambdaCallScope( - iter, - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .specsMatcher = Catch::Matchers::Equals("const")}); - checkTopLevel(iter); - REQUIRE_FALSE(iter()); - } - - SECTION("When unary lambda is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName("my_typeUnaryLambda", iter); - checkLambdaCallScope( - iter, - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .argListMatcher = Catch::Matchers::Equals("int"), - .specsMatcher = Catch::Matchers::Equals("const")}); - checkTopLevel(iter); - REQUIRE_FALSE(iter()); - } - - SECTION("When binary lambda is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName("my_typeBinaryLambda", iter); - checkLambdaCallScope( - iter, - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::RegexMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .argListMatcher = Catch::Matchers::Matches(R"(int,\s?float)"), - .specsMatcher = Catch::Matchers::Equals("const")}); - checkTopLevel(iter); - REQUIRE_FALSE(iter()); - } - - SECTION("When mutable lambda is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName("my_typeMutableLambda", iter); - checkLambdaCallScope(iter, function_info_matchers{}); - checkTopLevel(iter); - REQUIRE_FALSE(iter()); - } - - SECTION("When noexcept lambda is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName("my_typeNoexceptLambda", iter); - checkLambdaCallScope( - iter, - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .specsMatcher = Catch::Matchers::Equals("const")}); - checkTopLevel(iter); - REQUIRE_FALSE(iter()); - } - - SECTION("When nested lambda is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName("my_typeNestedLambda", iter); - checkLambdaCallScope( - iter, - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .specsMatcher = Catch::Matchers::Equals("const")}); - checkLambdaCallScope( - iter, - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .specsMatcher = Catch::Matchers::Equals("const")}); - checkTopLevel(iter); - REQUIRE_FALSE(iter()); - } - - SECTION("When complex nested lambda is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkLambdaName("my_typeComplexNestedLambda", iter); - checkLambdaCallScope( - iter, - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .specsMatcher = Catch::Matchers::Equals("const")}); - checkLambdaCallScope( - iter, - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::RegexMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .argListMatcher = Catch::Matchers::Matches(R"(int,\s?float)"), - .specsMatcher = Catch::Matchers::Equals("const")}); - checkTopLevel(iter); - REQUIRE_FALSE(iter()); - } -} - -namespace -{ - [[maybe_unused]] auto anon_classFreeFunction() - { - class - { - } constexpr obj [[maybe_unused]]{}; - - return obj; - } - - [[maybe_unused]] auto anon_structFreeFunction() - { - struct - { - } constexpr obj [[maybe_unused]]{}; - - return obj; - } - - [[maybe_unused]] auto anon_enumFreeFunction() - { - enum - { - } constexpr obj [[maybe_unused]]{}; - - return obj; - } - - [[maybe_unused]] auto my_typeFreeFunction() - { - struct my_type - { - }; - - return my_type{}; - } - - [[maybe_unused]] auto my_typeNoexceptFreeFunction() noexcept - { - struct my_type - { - }; - - return my_type{}; - } - - template - [[maybe_unused]] auto* my_typeTemplateFreeFunction() - { - struct my_type - { - } static constexpr obj [[maybe_unused]]{}; - - return &obj; - } -} - -TEST_CASE( - "printing::type::detail::ScopeIterator supports all kinds of free-functions.", - "[print][detail]") -{ - SECTION("When anon function-local type is given.") - { - auto const [expectedFunctionName, rawName] = GENERATE( - (table)({ - { "anon_classFreeFunction", name_of()}, - {"anon_structFreeFunction", name_of()}, - { "anon_enumFreeFunction", name_of()} - })); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkFunctionScope( - iter, - Catch::Matchers::Equals(expectedFunctionName), - function_info_matchers{}); - checkNamedScope( - iter, - !Catch::Matchers::IsEmpty()); - REQUIRE_FALSE(iter()); - } - - SECTION("When named function-local type is given.") - { - auto const [expectedFunctionName, rawName] = GENERATE( - (table)({ - { "my_typeFreeFunction", name_of()}, - {"my_typeNoexceptFreeFunction", name_of()} - })); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkFunctionScope( - iter, - Catch::Matchers::Equals(expectedFunctionName), - function_info_matchers{}); - checkNamedScope( - iter, - Catch::Matchers::Equals("my_type")); - REQUIRE_FALSE(iter()); - } - - SECTION("When named template-function-local type is given.") - { - StringT const rawName = name_of())>(); - CAPTURE(rawName); - - printing::type::detail::ScopeIterator iter{rawName}; - - REQUIRE(iter()); - checkTemplateFunctionScope( - iter, - Catch::Matchers::Equals("my_typeTemplateFreeFunction"), - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher, - Catch::Matchers::IsEmptyMatcher>{ - .argListMatcher = Catch::Matchers::Equals("int")}, - function_info_matchers{}); - checkNamedScope( - iter, - Catch::Matchers::Matches(R"(my_type const\s*\*)")); - REQUIRE_FALSE(iter()); - } -} - -TEST_CASE( - "printing::type::detail::ScopeIterator supports all kinds of function-pointer.", - "[print][detail]") -{ - SECTION("When a general function-pointer is given.") - { - StringT const rawName = name_of(); - CAPTURE(rawName); - - auto const scope = printing::type::detail::gather_scope_info(rawName); - REQUIRE_FALSE(scope.templateInfo); - REQUIRE(scope.functionInfo); - CHECK_THAT( - scope.functionInfo->argList, - Catch::Matchers::IsEmpty()); - CHECK_THAT( - scope.functionInfo->specs, - Catch::Matchers::IsEmpty()); - - SECTION("When visiting identifier.") - { - printing::type::detail::ScopeIterator iter{scope.identifier}; - - checkNamedScope( - iter, - Catch::Matchers::Equals("(*)")); - REQUIRE_FALSE(iter()); - } - - SECTION("When visiting return-type.") - { - printing::type::detail::ScopeIterator iter{scope.functionInfo->returnType}; - - REQUIRE(iter()); - checkFunctionScope( - iter, - Catch::Matchers::Equals("my_typeFreeFunction"), - function_info_matchers{}); - checkNamedScope( - iter, - Catch::Matchers::Equals("my_type")); - REQUIRE_FALSE(iter()); - } - } - - SECTION("When a template function-pointer is given.") - { - StringT const rawName = name_of)>(); - CAPTURE(rawName); - - auto const scope = printing::type::detail::gather_scope_info(rawName); - REQUIRE_FALSE(scope.templateInfo); - - REQUIRE(scope.functionInfo); - CHECK_THAT( - scope.functionInfo->argList, - Catch::Matchers::IsEmpty()); - CHECK_THAT( - scope.functionInfo->specs, - Catch::Matchers::IsEmpty()); - - SECTION("When visiting identifier.") - { - printing::type::detail::ScopeIterator iter{scope.identifier}; - - checkNamedScope( - iter, - Catch::Matchers::Equals("(*)")); - REQUIRE_FALSE(iter()); - } - - SECTION("When visiting return-type.") - { - printing::type::detail::ScopeIterator iter{scope.functionInfo->returnType}; - - REQUIRE(iter()); - checkTemplateFunctionScope( - iter, - Catch::Matchers::Equals("my_typeTemplateFreeFunction"), - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher, - Catch::Matchers::IsEmptyMatcher>{ - .argListMatcher = Catch::Matchers::Equals("int")}, - function_info_matchers{}); - checkNamedScope( - iter, - Catch::Matchers::Matches(R"(my_type const\s*\*)")); - REQUIRE_FALSE(iter()); - } - } - - SECTION("When a template-function-pointer in template-function-pointer is given.") - { - using type_t = decltype(&my_typeTemplateFreeFunction); - StringT const rawName = name_of)>(); - CAPTURE(rawName); - - auto const scope = printing::type::detail::gather_scope_info(rawName); - REQUIRE_FALSE(scope.templateInfo); - - REQUIRE(scope.functionInfo); - CHECK_THAT( - scope.functionInfo->argList, - Catch::Matchers::IsEmpty()); - CHECK_THAT( - scope.functionInfo->specs, - Catch::Matchers::IsEmpty()); - - SECTION("When visiting identifier.") - { - printing::type::detail::ScopeIterator iter{scope.identifier}; - - checkNamedScope( - iter, - Catch::Matchers::Equals("(*)")); - REQUIRE_FALSE(iter()); - } - - SECTION("When visiting return-type.") - { - printing::type::detail::ScopeIterator iter{scope.functionInfo->returnType}; - - REQUIRE(iter()); - - auto const fnScope = iter(); - REQUIRE(fnScope); - CHECK_THAT( - StringT{fnScope->identifier}, - Catch::Matchers::Equals("my_typeTemplateFreeFunction")); - - REQUIRE(fnScope->functionInfo); - CHECK_THAT( - fnScope->functionInfo->returnType, - Catch::Matchers::IsEmpty()); - CHECK_THAT( - fnScope->functionInfo->argList, - Catch::Matchers::IsEmpty()); - CHECK_THAT( - fnScope->functionInfo->specs, - Catch::Matchers::IsEmpty()); - - REQUIRE(fnScope->templateInfo); - - SECTION("When visiting the template argument.") - { - CAPTURE(fnScope->templateInfo->argList); - auto const argScope = printing::type::detail::gather_scope_info(fnScope->templateInfo->argList); - REQUIRE_FALSE(argScope.templateInfo); - REQUIRE(argScope.functionInfo); - CHECK_THAT( - argScope.functionInfo->argList, - Catch::Matchers::IsEmpty()); - CHECK_THAT( - argScope.functionInfo->specs, - Catch::Matchers::IsEmpty()); - CHECK_THAT( - StringT{argScope.functionInfo->returnType}, - Catch::Matchers::EndsWith("::my_type const *") - || Catch::Matchers::EndsWith("::my_type const*")); - CHECK_THAT( - StringT{argScope.identifier}, - Catch::Matchers::Equals("(*)")); - } - - checkNamedScope( - iter, - Catch::Matchers::Matches(R"(my_type const\s*\*)")); - REQUIRE_FALSE(iter()); - } - } -} - -namespace -{ - class special_operators - { - public: - [[nodiscard]] - auto operator<(int) const noexcept - { - struct my_type - { - }; - - return my_type{}; - } - - [[nodiscard]] - auto operator<=(int) const noexcept - { - struct my_type - { - }; - - return my_type{}; - } - - [[nodiscard]] - auto operator>(int) const noexcept - { - struct my_type - { - }; - - return my_type{}; - } - - [[nodiscard]] - auto operator>=(int) const noexcept - { - struct my_type - { - }; - - return my_type{}; - } - - [[nodiscard]] - auto operator<(std::string) const noexcept - { - struct my_type - { - [[nodiscard]] - auto operator>=(int) const noexcept - { - struct my_type - { - }; - - return my_type{}; - } - }; - - return my_type{}.operator>=(42); - } - - [[nodiscard]] - auto operator<=(std::string) const noexcept - { - struct my_type - { - [[nodiscard]] - auto operator>(int) const noexcept - { - struct my_type - { - }; - - return my_type{}; - } - }; - - return my_type{}.operator>(42); - } - - [[nodiscard]] - auto operator>(std::string) const noexcept - { - struct my_type - { - [[nodiscard]] - auto operator<=(int) const noexcept - { - struct my_type - { - }; - - return my_type{}; - } - }; - - return my_type{}.operator<=(42); - } - - [[nodiscard]] - auto operator>=(std::string) const noexcept - { - struct my_type - { - [[nodiscard]] - auto operator<(int) const noexcept - { - struct my_type - { - }; - - return my_type{}; - } - }; - - return my_type{}.operator<(42); - } - - [[nodiscard]] - auto operator<=>(int) const noexcept - { - struct my_type - { - }; - - return my_type{}; - } - }; -} - -TEST_CASE( - "printing::type::detail::ScopeIterator supports operator<, <=, >, >= and <=>.", - "[print][detail]") -{ - SECTION("When ordering operator is used.") - { - auto const [expectedFunctionName, rawName] = GENERATE( - (table)({ - {R"(operator-lt)", name_of()}, - {R"(operator-le)", name_of()}, - {R"(operator-gt)", name_of(42))>()}, - {R"(operator-ge)", name_of=(42))>()} - })); - CAPTURE(rawName); - - auto const scope = printing::type::detail::gather_scope_info(rawName); - REQUIRE_FALSE(scope.templateInfo); - REQUIRE_FALSE(scope.functionInfo); - - printing::type::detail::ScopeIterator iter{scope.identifier}; - - REQUIRE(iter()); - checkNamedScope( - iter, - Catch::Matchers::Equals("special_operators")); - checkFunctionScope( - iter, - Catch::Matchers::Equals(expectedFunctionName), - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .argListMatcher = Catch::Matchers::Equals("int"), - .specsMatcher = Catch::Matchers::Equals("const")}); - checkNamedScope( - iter, - Catch::Matchers::Equals("my_type")); - REQUIRE_FALSE(iter()); - } - - SECTION("When nested ordering operator is used.") - { - auto const [expectedFunctionName, expectedNestedFunctionName, rawName] = GENERATE( - (table)({ - {R"(operator-lt)", R"(operator-ge)", name_of()}, - {R"(operator-le)", R"(operator-gt)", name_of()}, - {R"(operator-gt)", R"(operator-le)", name_of(""))>()}, - {R"(operator-ge)", R"(operator-lt)", name_of=(""))>()} - })); - CAPTURE(rawName); - - auto const scope = printing::type::detail::gather_scope_info(rawName); - REQUIRE_FALSE(scope.templateInfo); - REQUIRE_FALSE(scope.functionInfo); - - printing::type::detail::ScopeIterator iter{scope.identifier}; - - REQUIRE(iter()); - checkNamedScope( - iter, - Catch::Matchers::Equals("special_operators")); - checkFunctionScope( - iter, - Catch::Matchers::Equals(expectedFunctionName), - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringContainsMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .argListMatcher = Catch::Matchers::ContainsSubstring("basic_string<"), - .specsMatcher = Catch::Matchers::Equals("const")}); - checkNamedScope( - iter, - Catch::Matchers::Equals("my_type")); - checkFunctionScope( - iter, - Catch::Matchers::Equals(expectedNestedFunctionName), - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .argListMatcher = Catch::Matchers::Equals("int"), - .specsMatcher = Catch::Matchers::Equals("const")}); - checkNamedScope( - iter, - Catch::Matchers::Equals("my_type")); - REQUIRE_FALSE(iter()); - } - - SECTION("When spaceship-operator is used.") - { - StringT const rawName = name_of(42))>(); - CAPTURE(rawName); - - auto const scope = printing::type::detail::gather_scope_info(rawName); - REQUIRE_FALSE(scope.templateInfo); - REQUIRE_FALSE(scope.functionInfo); - - printing::type::detail::ScopeIterator iter{scope.identifier}; - - REQUIRE(iter()); - checkNamedScope( - iter, - Catch::Matchers::Equals("special_operators")); - checkFunctionScope( - iter, - Catch::Matchers::Equals("operator-spaceship"), - function_info_matchers< - Catch::Matchers::IsEmptyMatcher, - Catch::Matchers::StringEqualsMatcher, - Catch::Matchers::StringEqualsMatcher>{ - .argListMatcher = Catch::Matchers::Equals("int"), - .specsMatcher = Catch::Matchers::Equals("const")}); - checkNamedScope( - iter, - Catch::Matchers::Equals("my_type")); - REQUIRE_FALSE(iter()); - } -} - -#endif From be06e7087adc5b4e7f0530206d8883b7bf3bae02 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 18:44:51 +0200 Subject: [PATCH 107/130] fix: parsing::NameParser handles all builtin-types correctly --- include/mimic++/printing/type/NameLexer.hpp | 3 + include/mimic++/printing/type/NameParser.hpp | 47 ++++++- .../printing/type/NameParserReductions.hpp | 4 +- .../printing/type/NameParserTokens.hpp | 8 ++ test/unit-tests/printing/TypeNameParser.cpp | 129 ++++++++++++++++++ .../printing/TypePostProcessing.cpp | 49 ++++++- 6 files changed, 235 insertions(+), 5 deletions(-) diff --git a/include/mimic++/printing/type/NameLexer.hpp b/include/mimic++/printing/type/NameLexer.hpp index 086cda74e..5aef467bf 100644 --- a/include/mimic++/printing/type/NameLexer.hpp +++ b/include/mimic++/printing/type/NameLexer.hpp @@ -39,6 +39,8 @@ namespace mimicpp::printing::type::lexing constexpr std::array visibilityKeywords = std::to_array({"public", "protected", "private"}); constexpr std::array specKeywords = std::to_array({"const", "constexpr", "volatile", "noexcept", "static"}); constexpr std::array contextKeywords = std::to_array({"operator", "struct", "class", "enum"}); + constexpr std::array typeKeywords = std::to_array( + {"auto", "void", "bool", "char", "char8_t", "char16_t", "char32_t", "wchar_t", "double", "float", "int", "long", "short", "signed", "unsigned"}); constexpr std::array otherKeywords = std::to_array({"new", "delete", "co_await"}); constexpr std::array digraphs = std::to_array({"and", "or", "xor", "not", "bitand", "bitor", "compl", "and_eq", "or_eq", "xor_eq", "not_eq"}); @@ -65,6 +67,7 @@ namespace mimicpp::printing::type::lexing texts::specKeywords, texts::contextKeywords, texts::otherKeywords, + texts::typeKeywords, texts::digraphs); std::ranges::sort(collection); diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index bde95e4da..f53831d91 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -104,6 +104,23 @@ namespace mimicpp::printing::type::parsing static constexpr lexing::keyword structKeyword{"struct"}; static constexpr lexing::keyword enumKeyword{"enum"}; + static constexpr std::array typeKeywordCollection = { + lexing::keyword{"auto"}, + lexing::keyword{"void"}, + lexing::keyword{"bool"}, + lexing::keyword{"char"}, + lexing::keyword{"char8_t"}, + lexing::keyword{"char16_t"}, + lexing::keyword{"char32_t"}, + lexing::keyword{"wchar_t"}, + lexing::keyword{"double"}, + lexing::keyword{"float"}, + lexing::keyword{"int"}, + lexing::keyword{"long"}, + lexing::keyword{"short"}, + lexing::keyword{"signed"}, + lexing::keyword{"unsigned"}}; + Visitor m_Visitor; StringViewT m_Content; lexing::NameLexer m_Lexer; @@ -162,8 +179,27 @@ namespace mimicpp::printing::type::parsing constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, [[maybe_unused]] lexing::space const& space) { - if (is_suffix_of(m_TokenStack)) - { + 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 (auto const* const nextKeyword = peek_if(); + nextKeyword + && id->is_builtin() + && util::contains(typeKeywordCollection, *nextKeyword)) + { + auto& curContent = std::get(id->content); + auto const [nextContent, _] = m_Lexer.next(); + // Merge both keywords by simply treating them as contiguous content. + MIMICPP_ASSERT(curContent.data() + curContent.size() == content.data(), "Violated expectation."); + MIMICPP_ASSERT(content.data() + content.size() = nextContent.data(), "Violated expectation."); + curContent = StringViewT{ + curContent.data(), + nextContent.data() + nextContent.size()}; + + return; + } + token::try_reduce_as_type(m_TokenStack); } @@ -224,6 +260,13 @@ namespace mimicpp::printing::type::parsing // 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}); + } } constexpr bool process_simple_operator() diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 2c3a35080..038576adb 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -687,8 +687,8 @@ namespace mimicpp::printing::type::parsing tokenStack.pop_back(); MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); - tokenStack.back().emplace( - Identifier::OperatorInfo{.symbol = std::move(targetType)}); + tokenStack.back() = Identifier{ + .content = Identifier::OperatorInfo{.symbol = std::move(targetType)}}; tokenStack.emplace_back(std::move(funCtx)); try_reduce_as_function_identifier(tokenStack); } diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp index 22149a7b2..cb1070d11 100644 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -251,6 +251,8 @@ namespace mimicpp::printing::type::parsing::token class Identifier { public: + bool isBuiltinType{false}; + struct OperatorInfo { using Symbol = std::variant>; @@ -285,6 +287,12 @@ namespace mimicpp::printing::type::parsing::token && id->starts_with("__"); } + [[nodiscard]] + constexpr bool is_builtin() const noexcept + { + return isBuiltinType; + } + template constexpr void operator()(Visitor& visitor) const { diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 838fac506..407fcecc9 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -142,6 +142,135 @@ TEST_CASE( } } +TEST_CASE( + "parsing::NameParser supports builtin-types.", + "[print][print::type]") +{ + StringT const type = GENERATE( + "auto", + "int", + "float", + "double", + "unsigned", + "signed", + "unsigned char", + "signed char", + "long long", + "unsigned long long", + "signed long long"); + + VisitorMock visitor{}; + ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("When plain type is given.") + { + StringT 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When qualified type is given.") + { + StringT const qualification = GENERATE("&", "&&", "*"); + StringT const spacing = GENERATE("", " "); + StringT 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When type is used as template-argument.") + { + StringT 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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When type is used as function-argument.") + { + StringT 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(); + 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When type is used as return-type.") + { + StringT 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_function.expect_call(); + + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } +} + TEST_CASE( "parsing::NameParser detects identifiers.", "[print][print::type]") diff --git a/test/unit-tests/printing/TypePostProcessing.cpp b/test/unit-tests/printing/TypePostProcessing.cpp index 07433ff52..f157a4b6d 100644 --- a/test/unit-tests/printing/TypePostProcessing.cpp +++ b/test/unit-tests/printing/TypePostProcessing.cpp @@ -196,9 +196,56 @@ namespace StringT const callOpScopePattern = R"(operator\(\)::)"; } +TEMPLATE_TEST_CASE( + "printing::type::prettify_type handles built-in types correctly.", + "[print][print::type]", + char, + wchar_t, + char8_t, + char16_t, + char32_t, + short, + int, + long, + long long) +{ + StringT const rawName = printing::type::type_name(); + CAPTURE(rawName); + + StringStreamT ss{}; + + SECTION("When explicit signed name is given.") + { + using T = std::make_signed_t; + StringT const name = printing::type::type_name(); + CAPTURE(name); + + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + name); + REQUIRE_THAT( + std::move(ss).str(), + Catch::Matchers::Matches(name)); + } + + SECTION("When unsigned name is given.") + { + using T = std::make_unsigned_t; + StringT const name = printing::type::type_name(); + CAPTURE(name); + + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + name); + REQUIRE_THAT( + std::move(ss).str(), + Catch::Matchers::Matches(name)); + } +} + TEST_CASE( "printing::type::prettify_type enhances names appearance.", - "[print]") + "[print][print::type]") { StringStreamT ss{}; From 0234e1f9535207411ff6aa6ec4b45b28a498bbb6 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 19:12:53 +0200 Subject: [PATCH 108/130] fix: correct assertion condition --- include/mimic++/printing/type/NameParser.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index f53831d91..f826ebe24 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -236,7 +236,7 @@ namespace mimicpp::printing::type::parsing auto& specs = token::get_or_emplace_specs(m_TokenStack); MIMICPP_ASSERT(!specs.layers.empty(), "Zero spec layers detected."); auto& top = specs.layers.back(); - MIMICPP_ASSERT(!top.isConst, "Specs is already volatile."); + MIMICPP_ASSERT(!top.isVolatile, "Specs is already volatile."); top.isVolatile = true; } else if (noexceptKeyword == keyword) From 68b97e50828c76079d89333a433c4c30d27eafad Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Tue, 6 May 2025 20:35:33 +0200 Subject: [PATCH 109/130] fix: parsing::NameParser treats `__int64` as an alias for `long long` --- include/mimic++/printing/type/NameLexer.hpp | 3 ++- include/mimic++/printing/type/NameParser.hpp | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/include/mimic++/printing/type/NameLexer.hpp b/include/mimic++/printing/type/NameLexer.hpp index 5aef467bf..152ee290e 100644 --- a/include/mimic++/printing/type/NameLexer.hpp +++ b/include/mimic++/printing/type/NameLexer.hpp @@ -40,7 +40,8 @@ namespace mimicpp::printing::type::lexing constexpr std::array specKeywords = std::to_array({"const", "constexpr", "volatile", "noexcept", "static"}); constexpr std::array contextKeywords = std::to_array({"operator", "struct", "class", "enum"}); constexpr std::array typeKeywords = std::to_array( - {"auto", "void", "bool", "char", "char8_t", "char16_t", "char32_t", "wchar_t", "double", "float", "int", "long", "short", "signed", "unsigned"}); + // The `__int64` keyword is used by msvc as an alias for `long long`. + {"auto", "void", "bool", "char", "char8_t", "char16_t", "char32_t", "wchar_t", "double", "float", "int", "long", "__int64", "short", "signed", "unsigned"}); constexpr std::array otherKeywords = std::to_array({"new", "delete", "co_await"}); constexpr std::array digraphs = std::to_array({"and", "or", "xor", "not", "bitand", "bitor", "compl", "and_eq", "or_eq", "xor_eq", "not_eq"}); diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index f826ebe24..bb0911b77 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -116,6 +116,7 @@ namespace mimicpp::printing::type::parsing lexing::keyword{"double"}, lexing::keyword{"float"}, lexing::keyword{"int"}, + lexing::keyword{"__int64"}, lexing::keyword{"long"}, lexing::keyword{"short"}, lexing::keyword{"signed"}, @@ -177,16 +178,23 @@ namespace mimicpp::printing::type::parsing util::unreachable(); } + [[nodiscard]] + constexpr bool merge_with_next_token() const noexcept + { + auto const* const keyword = peek_if(); + + return keyword + && util::contains(typeKeywordCollection, *keyword); + } + constexpr void handle_lexer_token([[maybe_unused]] StringViewT const content, [[maybe_unused]] lexing::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 (auto const* const nextKeyword = peek_if(); - nextKeyword - && id->is_builtin() - && util::contains(typeKeywordCollection, *nextKeyword)) + if (id->is_builtin() + && merge_with_next_token()) { auto& curContent = std::get(id->content); auto const [nextContent, _] = m_Lexer.next(); From 52ec37eed52fc34e6a6ce57880297a56a705d64b Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 20:56:12 +0200 Subject: [PATCH 110/130] fix: please AppleClang-16 and 17 by disabling case for char8_t --- test/unit-tests/printing/TypePostProcessing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit-tests/printing/TypePostProcessing.cpp b/test/unit-tests/printing/TypePostProcessing.cpp index f157a4b6d..ba8eed07a 100644 --- a/test/unit-tests/printing/TypePostProcessing.cpp +++ b/test/unit-tests/printing/TypePostProcessing.cpp @@ -201,7 +201,7 @@ TEMPLATE_TEST_CASE( "[print][print::type]", char, wchar_t, - char8_t, + // char8_t, this causes some linker-issues on AppleClang-16 and 17 char16_t, char32_t, short, From f6908aac7b4b00baff752db519f8df0ced494b92 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 21:27:03 +0200 Subject: [PATCH 111/130] test: add test case returning function-ptr --- .../printing/TypePostProcessing.cpp | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/unit-tests/printing/TypePostProcessing.cpp b/test/unit-tests/printing/TypePostProcessing.cpp index ba8eed07a..6e0baab02 100644 --- a/test/unit-tests/printing/TypePostProcessing.cpp +++ b/test/unit-tests/printing/TypePostProcessing.cpp @@ -887,6 +887,37 @@ TEST_CASE( + "my_type" R"(\))")); } + + SECTION("When function-ptr is returned.") + { + using ret_t = void (*)(); + StringT const rawName = printing::type::type_name(); + CAPTURE(rawName); + + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + rawName); + + REQUIRE_THAT( + ss.str(), + Catch::Matchers::Equals("void (*)() ()")); + } + + SECTION("When function-ptr, which returns a function-ptr, is returned.") + { + using ret1_t = void (*)(); + using ret2_t = ret1_t (*)(); + StringT const rawName = printing::type::type_name(); + CAPTURE(rawName); + + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + rawName); + + REQUIRE_THAT( + ss.str(), + Catch::Matchers::Equals("void (*)() (*)() ()")); + } } namespace From 74b34ba359b16a8804e023aafc4208019073ad0e Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 21:27:54 +0200 Subject: [PATCH 112/130] test: add cases for printing::type::lexing::is_space --- test/unit-tests/printing/TypeNameLexer.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/unit-tests/printing/TypeNameLexer.cpp b/test/unit-tests/printing/TypeNameLexer.cpp index f537a669b..da4c53698 100644 --- a/test/unit-tests/printing/TypeNameLexer.cpp +++ b/test/unit-tests/printing/TypeNameLexer.cpp @@ -78,6 +78,25 @@ namespace } } +TEST_CASE( + "printing::type::lexing::is_space determines, whether the given character is a space.", + "[print][print::type]") +{ + SECTION("When a space is given, returns true.") + { + char const input = GENERATE(' ', '\t'); + + CHECK(printing::type::lexing::is_space(input)); + } + + SECTION("When no space is given, returns false.") + { + char const input = GENERATE('a', '_', '-', '1'); + + CHECK(!printing::type::lexing::is_space(input)); + } +} + TEST_CASE( "printing::type::lexing::NameLexer extracts tokens from given input.", "[print][print::type]") From 52a06735ab4d0e5b13fe3b9b59769cc4951996f7 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 21:28:18 +0200 Subject: [PATCH 113/130] test: add cases for printing::type::lexing::is_digit --- test/unit-tests/printing/TypeNameLexer.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/unit-tests/printing/TypeNameLexer.cpp b/test/unit-tests/printing/TypeNameLexer.cpp index da4c53698..7aa75358b 100644 --- a/test/unit-tests/printing/TypeNameLexer.cpp +++ b/test/unit-tests/printing/TypeNameLexer.cpp @@ -97,6 +97,25 @@ TEST_CASE( } } +TEST_CASE( + "printing::type::lexing::is_digit determines, whether the given character is a digit.", + "[print][print::type]") +{ + SECTION("When a digit is given, returns true.") + { + char const input = GENERATE('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'); + + CHECK(printing::type::lexing::is_digit(input)); + } + + SECTION("When no space is given, returns false.") + { + char const input = GENERATE('a', 'B', '_', '-', ' '); + + CHECK(!printing::type::lexing::is_digit(input)); + } +} + TEST_CASE( "printing::type::lexing::NameLexer extracts tokens from given input.", "[print][print::type]") From 1e2ea8852433f1da214baa39e1028521185c66e9 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 21:36:47 +0200 Subject: [PATCH 114/130] cleanup: remove obsolete code-branch --- include/mimic++/printing/type/NameParser.hpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index bb0911b77..e73535835 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -576,14 +576,6 @@ namespace mimicpp::printing::type::parsing m_TokenStack.pop_back(); } - // Ignore call-convention. - if (auto const* const id = match_suffix(m_TokenStack); - id - && id->is_reserved()) - { - m_TokenStack.pop_back(); - } - // Ignore return-types. if (is_suffix_of(m_TokenStack)) { From 441987bed51749473e20449e4ae7d00344396331 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 22:05:04 +0200 Subject: [PATCH 115/130] fix: try_reduce_as_function_ptr ignores optional space --- include/mimic++/printing/type/NameParserReductions.hpp | 2 ++ test/unit-tests/printing/TypePostProcessing.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 038576adb..bfe712d38 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -417,6 +417,8 @@ namespace mimicpp::printing::type::parsing } } + ignore_space(pendingTokens); + auto* specs = match_suffix(pendingTokens); ScopeSequence* scopeSeq{}; if (specs && specs->has_ptr()) diff --git a/test/unit-tests/printing/TypePostProcessing.cpp b/test/unit-tests/printing/TypePostProcessing.cpp index 6e0baab02..e7cc9ffba 100644 --- a/test/unit-tests/printing/TypePostProcessing.cpp +++ b/test/unit-tests/printing/TypePostProcessing.cpp @@ -840,7 +840,7 @@ TEST_CASE( } TEST_CASE( - "printing::type::prettify_type type-names enhances function-pointer type-names appearance.", + "printing::type::prettify_type enhances function-pointer type-names appearance.", "[print]") { StringStreamT ss{}; From c7abf3dbdf2ddefbdd282a75770447a15e6acfe8 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Tue, 6 May 2025 22:13:14 +0200 Subject: [PATCH 116/130] fix: try_reduce_as_function_ptr ignores call-convention --- include/mimic++/printing/type/NameParserReductions.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index bfe712d38..3483c13f2 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -418,6 +418,8 @@ namespace mimicpp::printing::type::parsing } ignore_space(pendingTokens); + // Ignore call-convention. + ignore_reserved_identifier(pendingTokens); auto* specs = match_suffix(pendingTokens); ScopeSequence* scopeSeq{}; From 49d1f7f58119ac6835ca23649dda5c29b83bb1dd Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 23:12:55 +0200 Subject: [PATCH 117/130] test: add test-cases for msvc-like decorated function-ptrs --- test/unit-tests/printing/TypeNameParser.cpp | 67 +++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 407fcecc9..3863bb594 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -1735,6 +1735,73 @@ TEST_CASE( } } +TEST_CASE( + "parsing::NameParser handles decorated function-ptrs.", + "[print][print::type]") +{ + VisitorMock visitor{}; + ScopedSequence sequence{}; + + sequence += visitor.begin.expect_call(); + + SECTION("When function-ptr is given.") + { + StringT 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } + + SECTION("When member function-ptr is given.") + { + StringT 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(); + sequence += visitor.end_function_args.expect_call(); + + sequence += visitor.end_type.expect_call(); + sequence += visitor.end.expect_call(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); + } +} + TEST_CASE( "parsing::NameParser handles arbitrarily scoped identifiers.", "[print][print::type]") From 442df7d736a0aa1de49a940e394f5ec61286625b Mon Sep 17 00:00:00 2001 From: dnkpp Date: Tue, 6 May 2025 23:40:46 +0200 Subject: [PATCH 118/130] refactor: simplify get_or_emplace_specs --- .../printing/type/NameParserReductions.hpp | 10 ++----- test/unit-tests/printing/TypeNameParser.cpp | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 3483c13f2..5df2d6e06 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -703,14 +703,8 @@ namespace mimicpp::printing::type::parsing // 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. - MIMICPP_ASSERT(std::get(tokenStack.back()).is_reserved(), "Unexpected token."); - tokenStack.pop_back(); + [[maybe_unused]] bool const result = try_reduce_as_type(tokenStack); + MIMICPP_ASSERT(result, "Unexpected state."); } if (auto* const type = match_suffix(tokenStack)) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 3863bb594..8aee5c13b 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -436,6 +436,32 @@ TEST_CASE( } } +TEST_CASE( + "parsing::NameParser handles decorated types.", + "[print][print::type]") +{ + VisitorMock visitor{}; + ScopedSequence sequence{}; + + StringT const indirection = GENERATE("&", "&&", "*"); + StringT const spacing = GENERATE("", " "); + StringT 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(); + + printing::type::parsing::NameParser parser{std::ref(visitor), input}; + parser.parse_type(); +} + TEST_CASE( "parsing::NameParser detects templates.", "[print][print::type]") From 4b51460c4d860d36cc58325abc27239ef5fcbed0 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Wed, 7 May 2025 00:13:09 +0200 Subject: [PATCH 119/130] fix: revert changes from previous commit --- .../mimic++/printing/type/NameParserReductions.hpp | 11 +++++++++-- test/unit-tests/printing/TypeNameParser.cpp | 12 +++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 5df2d6e06..4ed77be9e 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -703,8 +703,15 @@ namespace mimicpp::printing::type::parsing // Maybe wo got something like `type&` and need to reduce that identifier to an actual `Type` token. if (is_suffix_of(tokenStack)) { - [[maybe_unused]] bool const result = try_reduce_as_type(tokenStack); - MIMICPP_ASSERT(result, "Unexpected state."); + 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`. + MIMICPP_ASSERT(std::get(tokenStack.back()).is_reserved(), "Unexpected token."); + tokenStack.pop_back(); } if (auto* const type = match_suffix(tokenStack)) diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 8aee5c13b..29187a350 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -2172,9 +2172,10 @@ TEST_CASE( SECTION("When decorated function local type is given.") { - StringViewT const visibility = GENERATE("public", "private", "protected"); - StringViewT const typeClass = GENERATE("struct", "class", "enum"); - StringT const input = StringT{typeClass} + " `" + StringT{visibility} + ": void __cdecl foo() __ptr64" + spacing + "'::my_type"; + StringT const visibility = GENERATE("public", "private", "protected"); + StringT const typeClass = GENERATE("struct", "class", "enum"); + StringT const refness = GENERATE("", "&", "&&"); + StringT const input = typeClass + " `" + visibility + ": void __cdecl foo() __ptr64" + spacing + refness + "'::my_type"; CAPTURE(input); sequence += visitor.begin_type.expect_call(); @@ -2194,6 +2195,11 @@ TEST_CASE( sequence += visitor.begin_function_args.expect_call(); 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(); From bffaac2d5146da7958680e635b203305b227a713 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 10 May 2025 14:08:13 +0200 Subject: [PATCH 120/130] feat: begin_template_args and begin_function_args reports their argument-count by additional arg --- .../printing/type/NameParserTokens.hpp | 10 +- .../printing/type/NamePrintVisitor.hpp | 4 +- test/unit-tests/printing/TypeNameParser.cpp | 147 +++++++++--------- 3 files changed, 81 insertions(+), 80 deletions(-) diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp index cb1070d11..712f9abde 100644 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -23,7 +23,7 @@ namespace mimicpp::printing::type::parsing { template concept parser_visitor = std::movable - && requires(std::unwrap_reference_t visitor, StringViewT content) { + && requires(std::unwrap_reference_t visitor, StringViewT content, std::ptrdiff_t count) { visitor.unrecognized(content); visitor.begin(); @@ -38,7 +38,7 @@ namespace mimicpp::printing::type::parsing visitor.add_identifier(content); visitor.add_arg(); - visitor.begin_template_args(); + visitor.begin_template_args(count); visitor.end_template_args(); visitor.add_const(); @@ -52,7 +52,7 @@ namespace mimicpp::printing::type::parsing visitor.end_function(); visitor.begin_return_type(); visitor.end_return_type(); - visitor.begin_function_args(); + visitor.begin_function_args(count); visitor.end_function_args(); visitor.begin_function_ptr(); @@ -355,7 +355,7 @@ namespace mimicpp::printing::type::parsing::token { auto& unwrapped = unwrap_visitor(visitor); - unwrapped.begin_function_args(); + unwrapped.begin_function_args(std::ranges::ssize(args.types)); std::invoke(args, unwrapped); unwrapped.end_function_args(); std::invoke(specs, unwrapped); @@ -601,7 +601,7 @@ namespace mimicpp::printing::type::parsing::token { auto& unwrapped = unwrap_visitor(visitor); - unwrapped.begin_template_args(); + unwrapped.begin_template_args(std::ranges::ssize(types)); std::invoke(*this, unwrapped); unwrapped.end_template_args(); } diff --git a/include/mimic++/printing/type/NamePrintVisitor.hpp b/include/mimic++/printing/type/NamePrintVisitor.hpp index a85eab00f..3bc539164 100644 --- a/include/mimic++/printing/type/NamePrintVisitor.hpp +++ b/include/mimic++/printing/type/NamePrintVisitor.hpp @@ -168,7 +168,7 @@ namespace mimicpp::printing::type print(content); } - constexpr void begin_template_args() + constexpr void begin_template_args([[maybe_unused]] std::ptrdiff_t const count) { m_Context.push_arg_sequence(); @@ -204,7 +204,7 @@ namespace mimicpp::printing::type print(" "); } - constexpr void begin_function_args() + constexpr void begin_function_args([[maybe_unused]] std::ptrdiff_t const count) { m_Context.push_arg_sequence(); diff --git a/test/unit-tests/printing/TypeNameParser.cpp b/test/unit-tests/printing/TypeNameParser.cpp index 29187a350..aded982dd 100644 --- a/test/unit-tests/printing/TypeNameParser.cpp +++ b/test/unit-tests/printing/TypeNameParser.cpp @@ -27,14 +27,14 @@ namespace Mock begin_type{{.name = "VisitorMock::begin_type"}}; Mock end_type{{.name = "VisitorMock::end_type"}}; - Mock begin_template_args{{.name = "VisitorMock::begin_template_args"}}; + Mock begin_template_args{{.name = "VisitorMock::begin_template_args"}}; Mock end_template_args{{.name = "VisitorMock::end_template_args"}}; Mock begin_function{{.name = "VisitorMock::begin_function"}}; Mock end_function{{.name = "VisitorMock::end_function"}}; Mock begin_return_type{{.name = "VisitorMock::begin_return_type"}}; Mock end_return_type{{.name = "VisitorMock::end_return_type"}}; - Mock begin_function_args{{.name = "VisitorMock::begin_function_args"}}; + Mock begin_function_args{{.name = "VisitorMock::begin_function_args"}}; Mock end_function_args{{.name = "VisitorMock::end_function_args"}}; Mock begin_function_ptr{{.name = "VisitorMock::begin_function_ptr"}}; @@ -205,7 +205,7 @@ TEST_CASE( sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + 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(); @@ -232,7 +232,7 @@ TEST_CASE( sequence += visitor.end_type.expect_call(); sequence += visitor.end_return_type.expect_call(); - sequence += visitor.begin_function_args.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(); @@ -259,7 +259,7 @@ TEST_CASE( sequence += visitor.end_type.expect_call(); sequence += visitor.end_return_type.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); @@ -479,7 +479,7 @@ TEST_CASE( sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(0); sequence += visitor.end_template_args.expect_call(); sequence += visitor.end_type.expect_call(); @@ -498,7 +498,7 @@ TEST_CASE( sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + 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(); @@ -519,7 +519,7 @@ TEST_CASE( sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(0); sequence += visitor.end_template_args.expect_call(); sequence += visitor.add_const.expect_call(); @@ -541,7 +541,7 @@ TEST_CASE( sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(2); sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("int"); @@ -572,7 +572,7 @@ TEST_CASE( sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(2); sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("int"); @@ -607,11 +607,11 @@ TEST_CASE( sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + 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(); + sequence += visitor.begin_template_args.expect_call(0); sequence += visitor.end_template_args.expect_call(); sequence += visitor.end_type.expect_call(); @@ -632,13 +632,13 @@ TEST_CASE( sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + 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(); + sequence += visitor.begin_template_args.expect_call(1); sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("int"); @@ -671,7 +671,7 @@ TEST_CASE( sequence += visitor.begin_scope.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(0); sequence += visitor.end_template_args.expect_call(); sequence += visitor.end_scope.expect_call(); @@ -717,7 +717,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); CHECKED_IF(!spec.empty()) @@ -750,13 +750,13 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + 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(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); CHECKED_IF(!spec.empty()) @@ -789,7 +789,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(1); sequence += visitor.begin_type.expect_call(); sequence += visitor.begin_scope.expect_call(); @@ -831,12 +831,12 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_function_args.expect_call(); + 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(); + 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(); @@ -879,7 +879,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(2); sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("char"); @@ -932,11 +932,11 @@ TEST_CASE( CHECKED_IF(!templateExpr.empty()) { - sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(0); sequence += visitor.end_template_args.expect_call(); } - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); CHECKED_IF(!scopeSpec.empty()) @@ -954,7 +954,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("fun"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); CHECKED_IF(!spec.empty()) @@ -992,7 +992,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); CHECKED_IF(!spec.empty()) @@ -1016,7 +1016,7 @@ TEST_CASE( sequence += visitor.begin_type.expect_call(); sequence += visitor.add_identifier.expect_call("bar"); - sequence += visitor.begin_template_args.expect_call(); + 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(); @@ -1028,7 +1028,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); CHECKED_IF(!spec.empty()) @@ -1070,7 +1070,7 @@ TEST_CASE( sequence += visitor.end_type.expect_call(); sequence += visitor.end_return_type.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); CHECKED_IF(!spec.empty()) @@ -1101,7 +1101,7 @@ TEST_CASE( sequence += visitor.end_type.expect_call(); sequence += visitor.end_return_type.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); CHECKED_IF(!spec.empty()) @@ -1125,7 +1125,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(1); { sequence += visitor.begin_function.expect_call(); @@ -1135,7 +1135,7 @@ TEST_CASE( sequence += visitor.end_type.expect_call(); sequence += visitor.end_return_type.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); CHECKED_IF(!spec.empty()) @@ -1191,7 +1191,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call(placeholder); - sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(0); sequence += visitor.end_template_args.expect_call(); sequence += visitor.end_type.expect_call(); @@ -1294,7 +1294,7 @@ TEST_CASE( sequence += visitor.end_return_type.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); @@ -1323,7 +1323,7 @@ TEST_CASE( } sequence += visitor.add_identifier.expect_call(placeholder); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); @@ -1356,7 +1356,7 @@ TEST_CASE( sequence += visitor.end_scope.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); @@ -1403,7 +1403,7 @@ TEST_CASE( sequence += visitor.add_ptr.expect_call(); sequence += visitor.end_function_ptr.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_type.expect_call(); @@ -1430,7 +1430,7 @@ TEST_CASE( sequence += visitor.add_ptr.expect_call(); sequence += visitor.end_function_ptr.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.add_noexcept.expect_call(); @@ -1460,7 +1460,7 @@ TEST_CASE( sequence += visitor.add_lvalue_ref.expect_call(); sequence += visitor.end_function_ptr.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.add_noexcept.expect_call(); @@ -1489,7 +1489,7 @@ TEST_CASE( sequence += visitor.add_ptr.expect_call(); sequence += visitor.end_function_ptr.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(2); sequence += visitor.begin_type.expect_call(); sequence += visitor.begin_scope.expect_call(); @@ -1538,7 +1538,7 @@ TEST_CASE( sequence += visitor.add_ptr.expect_call(); sequence += visitor.end_function_ptr.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_type.expect_call(); @@ -1572,7 +1572,7 @@ TEST_CASE( sequence += visitor.add_ptr.expect_call(); sequence += visitor.end_function_ptr.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.expect_spec_call(spec); @@ -1605,7 +1605,7 @@ TEST_CASE( sequence += visitor.add_ptr.expect_call(); sequence += visitor.end_function_ptr.expect_call(); - sequence += visitor.begin_function_args.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(); @@ -1615,7 +1615,7 @@ TEST_CASE( sequence += visitor.end_return_type.expect_call(); } - sequence += visitor.begin_function_args.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(); @@ -1649,7 +1649,7 @@ TEST_CASE( sequence += visitor.add_ptr.expect_call(); sequence += visitor.end_function_ptr.expect_call(); - sequence += visitor.begin_function_args.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(); @@ -1663,7 +1663,7 @@ TEST_CASE( sequence += visitor.add_ptr.expect_call(); sequence += visitor.end_function_ptr.expect_call(); - sequence += visitor.begin_function_args.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(); @@ -1693,7 +1693,7 @@ TEST_CASE( sequence += visitor.add_ptr.expect_call(); sequence += visitor.end_function_ptr.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(1); { sequence += visitor.begin_type.expect_call(); @@ -1707,7 +1707,7 @@ TEST_CASE( 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(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_type.expect_call(); @@ -1731,7 +1731,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(1); { sequence += visitor.begin_type.expect_call(); @@ -1745,7 +1745,7 @@ TEST_CASE( 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(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_type.expect_call(); @@ -1787,7 +1787,7 @@ TEST_CASE( sequence += visitor.add_ptr.expect_call(); sequence += visitor.end_function_ptr.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_type.expect_call(); @@ -1817,7 +1817,7 @@ TEST_CASE( sequence += visitor.add_ptr.expect_call(); sequence += visitor.end_function_ptr.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_type.expect_call(); @@ -1867,7 +1867,7 @@ TEST_CASE( sequence += visitor.begin_function.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(2); expect_args(); sequence += visitor.end_function_args.expect_call(); @@ -1893,7 +1893,7 @@ TEST_CASE( sequence += visitor.begin_function.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(2); expect_args(); sequence += visitor.end_function_args.expect_call(); @@ -1926,7 +1926,7 @@ TEST_CASE( sequence += visitor.begin_function.expect_call(); sequence += visitor.add_identifier.expect_call("bar"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(2); expect_args(); sequence += visitor.end_function_args.expect_call(); @@ -1953,7 +1953,7 @@ TEST_CASE( sequence += visitor.begin_function.expect_call(); sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(2); { sequence += visitor.begin_type.expect_call(); @@ -1975,7 +1975,7 @@ TEST_CASE( sequence += visitor.end_template_args.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.add_const.expect_call(); @@ -1989,7 +1989,7 @@ TEST_CASE( sequence += visitor.begin_function.expect_call(); sequence += visitor.add_identifier.expect_call("bar"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(2); expect_args(); sequence += visitor.end_function_args.expect_call(); @@ -2056,7 +2056,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); @@ -2096,7 +2096,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("fn0"); - sequence += visitor.begin_function_args.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.add_const.expect_call(); @@ -2127,7 +2127,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("fn1"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.add_const.expect_call(); @@ -2151,7 +2151,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("fn2"); - sequence += visitor.begin_function_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_lvalue_ref.expect_call(); @@ -2192,7 +2192,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("foo"); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); CHECKED_IF(!refness.empty()) @@ -2242,7 +2242,8 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call("()"); sequence += visitor.end_operator_identifier.expect_call(); - sequence += visitor.begin_function_args.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(); @@ -2367,7 +2368,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call(identifier); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); @@ -2386,7 +2387,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call(identifier); - sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(0); sequence += visitor.end_template_args.expect_call(); sequence += visitor.end_type.expect_call(); @@ -2471,7 +2472,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call(operatorSymbol); sequence += visitor.end_operator_identifier.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); @@ -2494,10 +2495,10 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call(operatorSymbol); sequence += visitor.end_operator_identifier.expect_call(); - sequence += visitor.begin_template_args.expect_call(); + sequence += visitor.begin_template_args.expect_call(0); sequence += visitor.end_template_args.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); @@ -2527,7 +2528,7 @@ TEST_CASE( sequence += visitor.add_identifier.expect_call(operatorSymbol); sequence += visitor.end_operator_identifier.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); @@ -2565,7 +2566,7 @@ TEST_CASE( sequence += visitor.end_type.expect_call(); sequence += visitor.end_operator_identifier.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); @@ -2594,7 +2595,7 @@ TEST_CASE( 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(); + 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(); @@ -2608,7 +2609,7 @@ TEST_CASE( } sequence += visitor.end_operator_identifier.expect_call(); - sequence += visitor.begin_function_args.expect_call(); + sequence += visitor.begin_function_args.expect_call(0); sequence += visitor.end_function_args.expect_call(); sequence += visitor.end_function.expect_call(); From 222e200f85e20b475a3d63b5d89fb8868a6d92bc Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 10 May 2025 15:06:29 +0200 Subject: [PATCH 121/130] fix: parsing::NameParser correctly handles conversion-operators --- include/mimic++/printing/type/NameParser.hpp | 2 +- .../printing/type/NameParserReductions.hpp | 38 +++--- .../printing/type/NamePrintVisitor.hpp | 2 +- .../printing/FunctionTypePostProcessing.cpp | 116 +++++++++++++++++- 4 files changed, 138 insertions(+), 20 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index e73535835..c1a8a36fe 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -51,7 +51,7 @@ namespace mimicpp::printing::type::parsing if (m_HasConversionOperator) { - token::reduce_as_conversion_operator_function_identifier(m_TokenStack); + token::try_reduce_as_conversion_operator_function_identifier(m_TokenStack); } else { diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 4ed77be9e..716fba332 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -678,23 +678,29 @@ namespace mimicpp::printing::type::parsing return false; } - inline void reduce_as_conversion_operator_function_identifier(TokenStack& tokenStack) + inline bool try_reduce_as_conversion_operator_function_identifier(TokenStack& tokenStack) { - MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); - auto funCtx = std::get(std::move(tokenStack.back())); - tokenStack.pop_back(); - - try_reduce_as_type(tokenStack); - MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); - auto targetType = std::make_shared( - std::get(std::move(tokenStack.back()))); - tokenStack.pop_back(); - - MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); - tokenStack.back() = Identifier{ - .content = Identifier::OperatorInfo{.symbol = std::move(targetType)}}; - tokenStack.emplace_back(std::move(funCtx)); - try_reduce_as_function_identifier(tokenStack); + if (auto* const funCtxPtr = match_suffix(tokenStack)) + { + FunctionContext funCtx = std::move(*funCtxPtr); + tokenStack.pop_back(); + + try_reduce_as_type(tokenStack); + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); + auto targetType = std::make_shared( + std::get(std::move(tokenStack.back()))); + tokenStack.pop_back(); + + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); + tokenStack.back() = Identifier{ + .content = Identifier::OperatorInfo{.symbol = std::move(targetType)}}; + tokenStack.emplace_back(std::move(funCtx)); + try_reduce_as_function_identifier(tokenStack); + + return true; + } + + return false; } [[nodiscard]] diff --git a/include/mimic++/printing/type/NamePrintVisitor.hpp b/include/mimic++/printing/type/NamePrintVisitor.hpp index 3bc539164..427be4a04 100644 --- a/include/mimic++/printing/type/NamePrintVisitor.hpp +++ b/include/mimic++/printing/type/NamePrintVisitor.hpp @@ -230,7 +230,7 @@ namespace mimicpp::printing::type constexpr void begin_operator_identifier() { - print("operator"); + print("operator "); } static constexpr void end_operator_identifier() diff --git a/test/unit-tests/printing/FunctionTypePostProcessing.cpp b/test/unit-tests/printing/FunctionTypePostProcessing.cpp index 48d57464a..a3ee8e0be 100644 --- a/test/unit-tests/printing/FunctionTypePostProcessing.cpp +++ b/test/unit-tests/printing/FunctionTypePostProcessing.cpp @@ -5,8 +5,7 @@ #include "mimic++/Stacktrace.hpp" #include "mimic++/printing/TypePrinter.hpp" - -#include +#include "mimic++/utilities/SourceLocation.hpp" using namespace mimicpp; @@ -288,6 +287,119 @@ TEST_CASE( } } +namespace +{ + struct conversion + { + operator util::SourceLocation() + { + return util::SourceLocation{}; + } + + operator util::SourceLocation() const + { + return util::SourceLocation{}; + } + + operator Stacktrace() + { + return stacktrace::current(); + } + + operator Stacktrace() const + { + return stacktrace::current(); + } + }; +} + +TEST_CASE( + "printing::type::prettify_function supports conversion-operators.", + "[print][print::type]") +{ + StringStreamT ss{}; + + SECTION("When getting function name via source_location") + { + SECTION("and when converted to simple type via non-const function.") + { + conversion conv{}; + auto const loc = static_cast(conv); + StringT const fnName = loc->function_name(); + CAPTURE(fnName); + + printing::type::prettify_function( + std::ostreambuf_iterator{ss}, + fnName); + + REQUIRE_THAT( + ss.str(), + Catch::Matchers::Matches( + anonNsScopePattern + + "conversion::" + + R"(operator mimicpp::util::SourceLocation\(\))")); + } + + SECTION("and when converted to simple type via const function.") + { + conversion const conv{}; + auto const loc = static_cast(conv); + StringT const fnName = loc->function_name(); + CAPTURE(fnName); + + printing::type::prettify_function( + std::ostreambuf_iterator{ss}, + fnName); + + REQUIRE_THAT( + ss.str(), + Catch::Matchers::Matches( + anonNsScopePattern + + "conversion::" + + R"(operator mimicpp::util::SourceLocation\(\) const)")); + } + } + + #if MIMICPP_DETAIL_HAS_WORKING_STACKTRACE_BACKEND + + SECTION("When getting function name via stacktrace") + { + SECTION("and when converted to simple type via non-const function.") + { + conversion conv{}; + auto const trace = static_cast(conv); + StringT const fnName = trace.description(0u); + CAPTURE(fnName); + + printing::type::prettify_function( + std::ostreambuf_iterator{ss}, + fnName); + + REQUIRE_THAT( + ss.str(), + Catch::Matchers::Matches(R"(operator mimicpp::Stacktrace)")); + } + + SECTION("When converted to simple type via const function.") + { + conversion const conv{}; + auto const trace = static_cast(conv); + StringT const fnName = trace.description(0u); + CAPTURE(fnName); + + printing::type::prettify_function( + std::ostreambuf_iterator{ss}, + fnName); + + REQUIRE_THAT( + ss.str(), + Catch::Matchers::Matches(R"(operator mimicpp::Stacktrace)")); + } + } + + #endif +} + #if MIMICPP_DETAIL_HAS_WORKING_STACKTRACE_BACKEND constexpr auto function_type_post_processing_lambda_stacktrace = [] { From 405be1ee5aa2138c0888746f3dc3eb70a4897917 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 10 May 2025 15:17:51 +0200 Subject: [PATCH 122/130] chore: adjust conversion-operator reduction --- include/mimic++/printing/type/NameParser.hpp | 6 ++- .../printing/type/NameParserReductions.hpp | 38 ++++++++----------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index c1a8a36fe..dc77cb598 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -49,9 +49,11 @@ namespace mimicpp::printing::type::parsing { parse(); - if (m_HasConversionOperator) + // Functions reported by stacktrace are not in actual function form. So, we need to check here. + if (m_HasConversionOperator + && is_suffix_of(m_TokenStack)) { - token::try_reduce_as_conversion_operator_function_identifier(m_TokenStack); + token::reduce_as_conversion_operator_function_identifier(m_TokenStack); } else { diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 716fba332..4ed77be9e 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -678,29 +678,23 @@ namespace mimicpp::printing::type::parsing return false; } - inline bool try_reduce_as_conversion_operator_function_identifier(TokenStack& tokenStack) + inline void reduce_as_conversion_operator_function_identifier(TokenStack& tokenStack) { - if (auto* const funCtxPtr = match_suffix(tokenStack)) - { - FunctionContext funCtx = std::move(*funCtxPtr); - tokenStack.pop_back(); - - try_reduce_as_type(tokenStack); - MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); - auto targetType = std::make_shared( - std::get(std::move(tokenStack.back()))); - tokenStack.pop_back(); - - MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); - tokenStack.back() = Identifier{ - .content = Identifier::OperatorInfo{.symbol = std::move(targetType)}}; - tokenStack.emplace_back(std::move(funCtx)); - try_reduce_as_function_identifier(tokenStack); - - return true; - } - - return false; + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); + auto funCtx = std::get(std::move(tokenStack.back())); + tokenStack.pop_back(); + + try_reduce_as_type(tokenStack); + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); + auto targetType = std::make_shared( + std::get(std::move(tokenStack.back()))); + tokenStack.pop_back(); + + MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); + tokenStack.back() = Identifier{ + .content = Identifier::OperatorInfo{.symbol = std::move(targetType)}}; + tokenStack.emplace_back(std::move(funCtx)); + try_reduce_as_function_identifier(tokenStack); } [[nodiscard]] From 476f50faa47f5d0e4ad2ae1f39412d1f129ba5a2 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 10 May 2025 15:18:02 +0200 Subject: [PATCH 123/130] fix: correct operator related tests --- .../printing/FunctionTypePostProcessing.cpp | 16 ++++++------ .../printing/TypePostProcessing.cpp | 26 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/unit-tests/printing/FunctionTypePostProcessing.cpp b/test/unit-tests/printing/FunctionTypePostProcessing.cpp index a3ee8e0be..43b9d81c4 100644 --- a/test/unit-tests/printing/FunctionTypePostProcessing.cpp +++ b/test/unit-tests/printing/FunctionTypePostProcessing.cpp @@ -208,7 +208,7 @@ TEST_CASE( + "::" + anonTypePattern + "::" - R"(operator\(\))" + R"(operator\s?\(\))" R"(\(\)\s?const)")); } @@ -238,7 +238,7 @@ TEST_CASE( + "::" + anonTypePattern + "::" - R"(operator\(\))" + R"(operator\s?\(\))" R"(\(\)\s?const)")); } @@ -486,7 +486,7 @@ TEST_CASE( #if MIMICPP_DETAIL_IS_GCC REQUIRE_THAT( ss.str(), - Catch::Matchers::Matches(R"(operator\(\))")); + Catch::Matchers::Matches(R"(operator\s?\(\))")); #else REQUIRE_THAT( ss.str(), @@ -510,7 +510,7 @@ TEST_CASE( #if MIMICPP_DETAIL_IS_GCC REQUIRE_THAT( ss.str(), - Catch::Matchers::Matches(R"(operator\(\))")); + Catch::Matchers::Matches(R"(operator\s?\(\))")); #else REQUIRE_THAT( ss.str(), @@ -540,7 +540,7 @@ TEST_CASE( #if MIMICPP_DETAIL_IS_GCC REQUIRE_THAT( ss.str(), - Catch::Matchers::Matches(R"(operator\(\))")); + Catch::Matchers::Matches(R"(operator\s?\(\))")); #else REQUIRE_THAT( ss.str(), @@ -575,7 +575,7 @@ TEST_CASE( #if MIMICPP_DETAIL_IS_GCC REQUIRE_THAT( ss.str(), - Catch::Matchers::Matches(R"(operator\(\))")); + Catch::Matchers::Matches(R"(operator\s?\(\))")); #else REQUIRE_THAT( ss.str(), @@ -612,7 +612,7 @@ TEST_CASE( #if MIMICPP_DETAIL_IS_GCC REQUIRE_THAT( ss.str(), - Catch::Matchers::Matches(R"(operator\(\))")); + Catch::Matchers::Matches(R"(operator\s?\(\))")); #else REQUIRE_THAT( ss.str(), @@ -639,7 +639,7 @@ TEST_CASE( #if MIMICPP_DETAIL_IS_GCC REQUIRE_THAT( ss.str(), - Catch::Matchers::Matches(R"(operator\(\))")); + Catch::Matchers::Matches(R"(operator\s?\(\))")); #else REQUIRE_THAT( ss.str(), diff --git a/test/unit-tests/printing/TypePostProcessing.cpp b/test/unit-tests/printing/TypePostProcessing.cpp index e7cc9ffba..2a28ff3a6 100644 --- a/test/unit-tests/printing/TypePostProcessing.cpp +++ b/test/unit-tests/printing/TypePostProcessing.cpp @@ -193,7 +193,7 @@ namespace StringT const anonNsScopePattern = R"(\{anon-ns\}::)"; StringT const anonTypePattern = R"((\$_\d+|\{unnamed type#\d+\}|))"; StringT const testCaseScopePattern = R"(CATCH2_INTERNAL_TEST_\d+::)"; - StringT const callOpScopePattern = R"(operator\(\)::)"; + StringT const callOpScopePattern = R"(operator\s?\(\)::)"; } TEMPLATE_TEST_CASE( @@ -635,7 +635,7 @@ TEST_CASE( Catch::Matchers::Matches( anonNsScopePattern + "outer_type::" - R"(operator\+::)" + R"(operator\s?\+::)" "my_type")); } } @@ -1227,10 +1227,10 @@ TEST_CASE( { auto const [expectedFunctionName, rawName] = GENERATE( (table)({ - { R"(operator<)", printing::type::type_name()}, - {R"(operator<=)", printing::type::type_name()}, - { R"(operator>)", printing::type::type_name(42))>()}, - {R"(operator>=)", printing::type::type_name=(42))>()} + { R"(operator\s?<)", printing::type::type_name()}, + {R"(operator\s?<=)", printing::type::type_name()}, + { R"(operator\s?>)", printing::type::type_name(42))>()}, + {R"(operator\s?>=)", printing::type::type_name=(42))>()} })); CAPTURE(rawName); @@ -1250,10 +1250,10 @@ TEST_CASE( { auto const [expectedFunctionName, expectedNestedFunctionName, rawName] = GENERATE( (table)({ - { R"(operator<)", R"(operator>=)", printing::type::type_name()}, - {R"(operator<=)", R"(operator>)", printing::type::type_name()}, - { R"(operator>)", R"(operator<=)", printing::type::type_name(""))>()}, - {R"(operator>=)", R"(operator<)", printing::type::type_name=(""))>()} + { R"(operator\s?<)", R"(operator\s?>=)", printing::type::type_name()}, + {R"(operator\s?<=)", R"(operator\s?>)", printing::type::type_name()}, + { R"(operator\s?>)", R"(operator\s?<=)", printing::type::type_name(""))>()}, + {R"(operator\s?>=)", R"(operator\s?<)", printing::type::type_name=(""))>()} })); CAPTURE(rawName); @@ -1284,7 +1284,7 @@ TEST_CASE( Catch::Matchers::Matches( anonNsScopePattern + "special_operators::" - + "operator<=>::" + + R"(operator\s?<=>::)" + "my_type")); } } @@ -1308,7 +1308,7 @@ TEST_CASE( Catch::Matchers::Matches( anonNsScopePattern + "special_operators::" - + R"(operator\(\)::)" + + R"(operator\s?\(\)::)" + "my_type")); } @@ -1327,7 +1327,7 @@ TEST_CASE( #else anonNsScopePattern + #endif - R"(special_operators::operator\(\)::my_type )"; + R"(special_operators::operator\s?\(\)::my_type )"; REQUIRE_THAT( ss.str(), From bcab2d97a4975c304af240c3ddde0e74d17188d5 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 10 May 2025 15:49:35 +0200 Subject: [PATCH 124/130] fix: please clang --- .../printing/type/NameParserTokens.hpp | 21 +++++++------ .../printing/FunctionTypePostProcessing.cpp | 30 ++++++++++++------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp index 712f9abde..1ad40b016 100644 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ b/include/mimic++/printing/type/NameParserTokens.hpp @@ -351,15 +351,7 @@ namespace mimicpp::printing::type::parsing::token Specs specs{}; template - constexpr void 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); - } + constexpr void operator()(Visitor& visitor) const; }; class FunctionIdentifier @@ -606,6 +598,17 @@ namespace mimicpp::printing::type::parsing::token 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: diff --git a/test/unit-tests/printing/FunctionTypePostProcessing.cpp b/test/unit-tests/printing/FunctionTypePostProcessing.cpp index 43b9d81c4..1d2d58593 100644 --- a/test/unit-tests/printing/FunctionTypePostProcessing.cpp +++ b/test/unit-tests/printing/FunctionTypePostProcessing.cpp @@ -82,7 +82,7 @@ namespace StringT const topLevelLambdaPattern = R"((\$_\d+|lambda(#\d+)?|\(anonymous class\)))"; - StringT const lambdaCallOpPattern = topLevelLambdaPattern + R"(::(operator)?\(\))"; + StringT const lambdaCallOpPattern = topLevelLambdaPattern + R"(::(operator\s?)?\(\))"; StringT const anonNsScopePattern = R"(\{anon-ns\}::)"; StringT const anonTypePattern = R"((\$_\d+|||\(anonymous (class|struct|enum)\)))"; @@ -335,9 +335,11 @@ TEST_CASE( REQUIRE_THAT( ss.str(), Catch::Matchers::Matches( - anonNsScopePattern + // Clang adds a return type here. + "(util::SourceLocation )?" + + anonNsScopePattern + "conversion::" - + R"(operator mimicpp::util::SourceLocation\(\))")); + + R"(operator (mimicpp::util::)?SourceLocation\(\))")); } SECTION("and when converted to simple type via const function.") @@ -354,9 +356,11 @@ TEST_CASE( REQUIRE_THAT( ss.str(), Catch::Matchers::Matches( - anonNsScopePattern + // Clang adds a return type here. + "(util::SourceLocation )?" + + anonNsScopePattern + "conversion::" - + R"(operator mimicpp::util::SourceLocation\(\) const)")); + + R"(operator (mimicpp::util::)?SourceLocation\(\) const)")); } } @@ -377,7 +381,9 @@ TEST_CASE( REQUIRE_THAT( ss.str(), - Catch::Matchers::Matches(R"(operator mimicpp::Stacktrace)")); + Catch::Matchers::Matches( + R"((\{anon-ns\}::conversion::)?)" + R"(operator mimicpp::Stacktrace(\(\))?)")); } SECTION("When converted to simple type via const function.") @@ -393,7 +399,9 @@ TEST_CASE( REQUIRE_THAT( ss.str(), - Catch::Matchers::Matches(R"(operator mimicpp::Stacktrace)")); + Catch::Matchers::Matches( + R"((\{anon-ns\}::conversion::)?)" + R"(operator mimicpp::Stacktrace(\(\)\s?const)?)")); } } @@ -583,8 +591,8 @@ TEST_CASE( testCasePattern + "::" + anonTypePattern - + R"(::operator\(\))" - R"((\(\)(\s?const)?)?)")); + + R"(::operator\s?\(\))" + R"((\(\)(\s?const)?)?)")); #endif } @@ -620,8 +628,8 @@ TEST_CASE( testCasePattern + "::" + anonTypePattern - + R"(::operator\(\))" - R"((\(\)(\s?const)?)?)")); + + R"(::operator\s?\(\))" + R"((\(\)(\s?const)?)?)")); #endif } From 34a3a57ebdc658757dbce17ed308ab593d5db0e6 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 10 May 2025 16:10:24 +0200 Subject: [PATCH 125/130] fix: correct TestAssert.hpp integration --- examples/CMakeLists.txt | 1 - test/custom-stacktrace-tests/CMakeLists.txt | 1 - test/unit-tests/CMakeLists.txt | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 26fba009c..d897a8458 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -34,7 +34,6 @@ enable_sanitizers(${TARGET_NAME}) find_package(Catch2 REQUIRED) find_package(trompeloeil REQUIRED) -find_package(libassert REQUIRED) target_link_libraries(${TARGET_NAME} PRIVATE mimicpp::mimicpp diff --git a/test/custom-stacktrace-tests/CMakeLists.txt b/test/custom-stacktrace-tests/CMakeLists.txt index 79109d020..4e8705aeb 100644 --- a/test/custom-stacktrace-tests/CMakeLists.txt +++ b/test/custom-stacktrace-tests/CMakeLists.txt @@ -20,7 +20,6 @@ enable_sanitizers(${TARGET_NAME}) find_package(Catch2 REQUIRED) find_package(trompeloeil REQUIRED) -find_package(libassert REQUIRED) target_link_libraries(${TARGET_NAME} PRIVATE mimicpp::mimicpp diff --git a/test/unit-tests/CMakeLists.txt b/test/unit-tests/CMakeLists.txt index ac0a38808..26e981b7c 100644 --- a/test/unit-tests/CMakeLists.txt +++ b/test/unit-tests/CMakeLists.txt @@ -37,7 +37,6 @@ enable_sanitizers(${TARGET_NAME}) find_package(Catch2 REQUIRED) find_package(trompeloeil REQUIRED) -find_package(libassert REQUIRED) target_link_libraries(${TARGET_NAME} PRIVATE mimicpp::mimicpp @@ -48,8 +47,8 @@ target_link_libraries(${TARGET_NAME} target_precompile_headers(${TARGET_NAME} PRIVATE - "Catch2FallbackStringifier.hpp" + "Catch2FallbackStringifier.hpp" ) From e0c79b87a80d0fab55ef9e148e18fde610db5c75 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 10 May 2025 17:08:22 +0200 Subject: [PATCH 126/130] fix: make try_reduce_as_regular_type more robust --- .../mimic++/printing/type/NameParserReductions.hpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 4ed77be9e..25617fed6 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -598,9 +598,14 @@ namespace mimicpp::printing::type::parsing auto* const prefixSpecs = match_suffix(pendingTokens); if (prefixSpecs) { - [[maybe_unused]] auto& layers = prefixSpecs->layers; - MIMICPP_ASSERT(token::Specs::Refness::none == prefixSpecs->refness && !prefixSpecs->isNoexcept, "Invalid prefix specs."); - MIMICPP_ASSERT(1u == layers.size(), "Prefix specs can not have more than one layer."); + // 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); } From 63884634b8e5e85c28f9529467217f36ce38f10a Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 10 May 2025 17:24:09 +0200 Subject: [PATCH 127/130] fix: reduce_as_conversion_operator_function_identifier is more permissive --- include/mimic++/printing/type/NameParser.hpp | 4 +--- .../mimic++/printing/type/NameParserReductions.hpp | 13 ++++++++++--- .../printing/FunctionTypePostProcessing.cpp | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp index dc77cb598..e73535835 100644 --- a/include/mimic++/printing/type/NameParser.hpp +++ b/include/mimic++/printing/type/NameParser.hpp @@ -49,9 +49,7 @@ namespace mimicpp::printing::type::parsing { parse(); - // Functions reported by stacktrace are not in actual function form. So, we need to check here. - if (m_HasConversionOperator - && is_suffix_of(m_TokenStack)) + if (m_HasConversionOperator) { token::reduce_as_conversion_operator_function_identifier(m_TokenStack); } diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index 25617fed6..fc0d0f212 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -685,9 +685,16 @@ namespace mimicpp::printing::type::parsing inline void reduce_as_conversion_operator_function_identifier(TokenStack& tokenStack) { - MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); - auto funCtx = std::get(std::move(tokenStack.back())); - tokenStack.pop_back(); + // Functions reported by stacktrace are not in actual function form. + // As `operator T()` can not contain any arguments, we can simply create a dummy argument-sequence. + // Unfortunately, we can not detect whether it's e.g. `const` or `noexcept`. + // Todo: This issue should be somehow addressed. + FunctionContext funCtx{}; + if (auto* const ctx = match_suffix(tokenStack)) + { + funCtx = std::move(*ctx); + tokenStack.pop_back(); + } try_reduce_as_type(tokenStack); MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); diff --git a/test/unit-tests/printing/FunctionTypePostProcessing.cpp b/test/unit-tests/printing/FunctionTypePostProcessing.cpp index 1d2d58593..23f76631a 100644 --- a/test/unit-tests/printing/FunctionTypePostProcessing.cpp +++ b/test/unit-tests/printing/FunctionTypePostProcessing.cpp @@ -401,7 +401,7 @@ TEST_CASE( ss.str(), Catch::Matchers::Matches( R"((\{anon-ns\}::conversion::)?)" - R"(operator mimicpp::Stacktrace(\(\)\s?const)?)")); + R"(operator mimicpp::Stacktrace(\(\)(\s?const)?)?)")); } } From f6b5527107d5e77d37aba4417da61dd8b23ad4f3 Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 10 May 2025 17:32:57 +0200 Subject: [PATCH 128/130] chore: mark test case as mayfail on msvc --- .../printing/FunctionTypePostProcessing.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/unit-tests/printing/FunctionTypePostProcessing.cpp b/test/unit-tests/printing/FunctionTypePostProcessing.cpp index 23f76631a..a0bbabaa0 100644 --- a/test/unit-tests/printing/FunctionTypePostProcessing.cpp +++ b/test/unit-tests/printing/FunctionTypePostProcessing.cpp @@ -313,9 +313,15 @@ namespace }; } +#ifdef MIMICPP_DETAIL_IS_MSVC + #define MAYFAIL_ON_MSVC "[!mayfail]" +#else + #define MAYFAIL_ON_MSVC +#endif + TEST_CASE( "printing::type::prettify_function supports conversion-operators.", - "[print][print::type]") + MAYFAIL_ON_MSVC "[print][print::type]") { StringStreamT ss{}; @@ -383,7 +389,7 @@ TEST_CASE( ss.str(), Catch::Matchers::Matches( R"((\{anon-ns\}::conversion::)?)" - R"(operator mimicpp::Stacktrace(\(\))?)")); + R"(operator (mimicpp::)?Stacktrace(\(\))?)")); } SECTION("When converted to simple type via const function.") @@ -401,7 +407,7 @@ TEST_CASE( ss.str(), Catch::Matchers::Matches( R"((\{anon-ns\}::conversion::)?)" - R"(operator mimicpp::Stacktrace(\(\)(\s?const)?)?)")); + R"(operator (mimicpp::)?Stacktrace(\(\)(\s?const)?)?)")); } } From d4cf406db15b0f4933b04dd999ccf2c91d19bc00 Mon Sep 17 00:00:00 2001 From: dnkpp Date: Sat, 10 May 2025 18:11:23 +0200 Subject: [PATCH 129/130] chore: adjust reduce_as_conversion_operator_function_identifier's handling of operators in non-function form --- .../printing/type/NameParserReductions.hpp | 16 +++++++++------- .../printing/FunctionTypePostProcessing.cpp | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp index fc0d0f212..a2c3e1d44 100644 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ b/include/mimic++/printing/type/NameParserReductions.hpp @@ -685,11 +685,9 @@ namespace mimicpp::printing::type::parsing inline void reduce_as_conversion_operator_function_identifier(TokenStack& tokenStack) { - // Functions reported by stacktrace are not in actual function form. - // As `operator T()` can not contain any arguments, we can simply create a dummy argument-sequence. - // Unfortunately, we can not detect whether it's e.g. `const` or `noexcept`. - // Todo: This issue should be somehow addressed. - FunctionContext funCtx{}; + // 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); @@ -705,8 +703,12 @@ namespace mimicpp::printing::type::parsing MIMICPP_ASSERT(is_suffix_of(tokenStack), "Invalid state"); tokenStack.back() = Identifier{ .content = Identifier::OperatorInfo{.symbol = std::move(targetType)}}; - tokenStack.emplace_back(std::move(funCtx)); - try_reduce_as_function_identifier(tokenStack); + + if (funCtx) + { + tokenStack.emplace_back(*std::move(funCtx)); + try_reduce_as_function_identifier(tokenStack); + } } [[nodiscard]] diff --git a/test/unit-tests/printing/FunctionTypePostProcessing.cpp b/test/unit-tests/printing/FunctionTypePostProcessing.cpp index a0bbabaa0..921582943 100644 --- a/test/unit-tests/printing/FunctionTypePostProcessing.cpp +++ b/test/unit-tests/printing/FunctionTypePostProcessing.cpp @@ -407,7 +407,7 @@ TEST_CASE( ss.str(), Catch::Matchers::Matches( R"((\{anon-ns\}::conversion::)?)" - R"(operator (mimicpp::)?Stacktrace(\(\)(\s?const)?)?)")); + R"(operator (mimicpp::)?Stacktrace(\(\)\s?const)?)")); } } From ef2c1f82cf0c7d6a63d46ce5f72c989b8beebdcc Mon Sep 17 00:00:00 2001 From: Dominic Koepke Date: Sat, 10 May 2025 19:11:25 +0200 Subject: [PATCH 130/130] test: make conversion-operator stacktrace test more permissive to match results on clang-cl --- test/unit-tests/printing/FunctionTypePostProcessing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit-tests/printing/FunctionTypePostProcessing.cpp b/test/unit-tests/printing/FunctionTypePostProcessing.cpp index 921582943..a0bbabaa0 100644 --- a/test/unit-tests/printing/FunctionTypePostProcessing.cpp +++ b/test/unit-tests/printing/FunctionTypePostProcessing.cpp @@ -407,7 +407,7 @@ TEST_CASE( ss.str(), Catch::Matchers::Matches( R"((\{anon-ns\}::conversion::)?)" - R"(operator (mimicpp::)?Stacktrace(\(\)\s?const)?)")); + R"(operator (mimicpp::)?Stacktrace(\(\)(\s?const)?)?)")); } }