diff --git a/.clang-format b/.clang-format index 06d192d0e9..3a42f09ded 100644 --- a/.clang-format +++ b/.clang-format @@ -48,7 +48,7 @@ KeepEmptyLines: # Alignment AlignAfterOpenBracket: AlwaysBreak -AlignArrayOfStructures: Right +AlignArrayOfStructures: Left AlignConsecutiveBitFields: None AlignConsecutiveDeclarations: None AlignConsecutiveAssignments: None diff --git a/include/mimic++/Utilities.hpp b/include/mimic++/Utilities.hpp index ed658ba9ab..cd5833a449 100644 --- a/include/mimic++/Utilities.hpp +++ b/include/mimic++/Utilities.hpp @@ -1,4 +1,4 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 2025. +// Copyright Dominic (DNKpp) Koepke 2024-2026. // 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) @@ -11,6 +11,7 @@ #include "mimic++/utilities/C++23Backports.hpp" #include "mimic++/utilities/C++26Backports.hpp" #include "mimic++/utilities/Concepts.hpp" +#include "mimic++/utilities/CopyableBox.hpp" #include "mimic++/utilities/PassKey.hpp" #include "mimic++/utilities/PriorityTag.hpp" #include "mimic++/utilities/SourceLocation.hpp" diff --git a/include/mimic++/config/Config.hpp b/include/mimic++/config/Config.hpp index 5421aada9e..ea79241370 100644 --- a/include/mimic++/config/Config.hpp +++ b/include/mimic++/config/Config.hpp @@ -1,4 +1,4 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 2025. +// Copyright Dominic (DNKpp) Koepke 2024-2026. // 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) @@ -69,6 +69,17 @@ #define MIMICPP_DETAIL_CONSTEXPR_VECTOR inline #endif +// Requires constexpr vector and constexpr optional +// clang-format off +// Prevent number from getting decorated with '. +#if 201907L <= __cpp_lib_constexpr_vector \ + && 202106L <= __cpp_lib_optional + // clang-format on + #define MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES constexpr +#else + #define MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES inline +#endif + // gcc 10 requires a workaround, due to some ambiguities. // see: https://github.com/DNKpp/mimicpp/issues/151 #if MIMICPP_DETAIL_IS_GCC \ @@ -76,4 +87,8 @@ #define MIMICPP_DETAIL_STD_GET_WORKAROUND 1 #endif +#ifdef __cpp_lib_source_location + #define MIMICPP_DETAIL_HAS_SOURCE_LOCATION 1 +#endif + #endif diff --git a/include/mimic++/printing/TypePrinter.hpp b/include/mimic++/printing/TypePrinter.hpp index fe9c203ddd..6218fdb9a1 100644 --- a/include/mimic++/printing/TypePrinter.hpp +++ b/include/mimic++/printing/TypePrinter.hpp @@ -1,4 +1,4 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 2025. +// Copyright Dominic (DNKpp) Koepke 2024-2026. // 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) @@ -7,10 +7,11 @@ #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/Lexer.hpp" +#include "mimic++/printing/type/Parser.hpp" +#include "mimic++/printing/type/ParserState.hpp" #include "mimic++/printing/type/PrintType.hpp" +#include "mimic++/printing/type/PrintVisitor.hpp" #include "mimic++/printing/type/Signature.hpp" #include "mimic++/printing/type/Templated.hpp" diff --git a/include/mimic++/printing/state/C++23Backports.hpp b/include/mimic++/printing/state/C++23Backports.hpp index 36109499cb..4c3347a9d6 100644 --- a/include/mimic++/printing/state/C++23Backports.hpp +++ b/include/mimic++/printing/state/C++23Backports.hpp @@ -86,6 +86,7 @@ namespace mimicpp::printing::detail::state }; template + requires(!std::ranges::forward_range) struct cxx23_backport_printer { template diff --git a/include/mimic++/printing/state/CommonTypes.hpp b/include/mimic++/printing/state/CommonTypes.hpp index eb8ccd0bb1..6ae7064124 100644 --- a/include/mimic++/printing/state/CommonTypes.hpp +++ b/include/mimic++/printing/state/CommonTypes.hpp @@ -1,4 +1,4 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 2026. +// Copyright Dominic (DNKpp) Koepke 2024-2026. // 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) @@ -19,6 +19,7 @@ #include "mimic++/printing/type/PrintType.hpp" #include "mimic++/utilities/C++20Compatibility.hpp" #include "mimic++/utilities/C++23Backports.hpp" // unreachable +#include "mimic++/utilities/SourceLocation.hpp" #ifndef MIMICPP_DETAIL_IS_MODULE #include @@ -70,6 +71,20 @@ struct mimicpp::printing::detail::state::common_type_printer + struct common_type_printer + { + template + static constexpr OutIter print(OutIter out, util::SourceLocation const& loc) + { + return detail::print_source_location( + std::move(out), + loc.file_name(), + loc.line(), + loc.function_name()); + } + }; + template <> struct common_type_printer { diff --git a/include/mimic++/printing/type/NameLexer.hpp b/include/mimic++/printing/type/Lexer.hpp similarity index 82% rename from include/mimic++/printing/type/NameLexer.hpp rename to include/mimic++/printing/type/Lexer.hpp index f0d59a4f61..714a62eab3 100644 --- a/include/mimic++/printing/type/NameLexer.hpp +++ b/include/mimic++/printing/type/Lexer.hpp @@ -1,10 +1,10 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 2025. +// Copyright Dominic (DNKpp) Koepke 2024-2026. // 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 +#ifndef MIMICPP_PRINTING_TYPE_LEXER_HPP +#define MIMICPP_PRINTING_TYPE_LEXER_HPP #pragma once @@ -24,23 +24,33 @@ namespace mimicpp::printing::type::lexing { + // This is a constexpr friendly version of `std::isspace`. // see: https://en.cppreference.com/w/cpp/string/byte/isspace inline auto constexpr is_space = [](char const c) noexcept { - return static_cast( - std::isspace(static_cast(c))); + switch (c) + { + case ' ': [[fallthrough]]; + case '\f': [[fallthrough]]; + case '\n': [[fallthrough]]; + case '\r': [[fallthrough]]; + case '\t': [[fallthrough]]; + case '\v': return true; + + default: return false; + } }; + // This is a constexpr friendly version of `std::isdigit`. // see: https://en.cppreference.com/w/cpp/string/byte/isdigit inline auto constexpr is_digit = [](char const c) noexcept { - return static_cast( - std::isdigit(static_cast(c))); + return '0' <= c && c <= '9'; }; namespace texts { // just list the noteworthy ones here inline std::array constexpr visibilityKeywords = std::to_array({"public", "protected", "private"}); - inline std::array constexpr specKeywords = std::to_array({"const", "constexpr", "volatile", "noexcept", "static"}); + inline std::array constexpr specKeywords = std::to_array({"const", "constexpr", "volatile", "noexcept", "static", "mutable"}); inline std::array constexpr contextKeywords = std::to_array({"operator", "struct", "class", "enum"}); inline std::array constexpr typeKeywords = std::to_array( // The `__int64` keyword is used by msvc as an alias for `long long`. @@ -57,8 +67,10 @@ namespace mimicpp::printing::type::lexing inline std::array constexpr bitArithmetic = std::to_array({"~", "&", "|", "^", "<<", ">>"}); inline std::array constexpr logical = std::to_array({"!", "&&", "||"}); inline std::array constexpr access = std::to_array({".", ".*", "->", "->*"}); - inline std::array constexpr specialAngles = std::to_array({"<:", ":>", "<%", "%>"}); inline std::array constexpr rest = std::to_array({"::", ";", ",", ":", "...", "?"}); + + // These operators exist, but are in fact alternative versions and will probably never encountered. + // inline std::array constexpr specialAngles = std::to_array({"<:", ":>", "<%", "%>"}); } [[nodiscard]] @@ -91,7 +103,6 @@ namespace mimicpp::printing::type::lexing 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!"); @@ -132,6 +143,12 @@ namespace mimicpp::printing::type::lexing return textCollection[m_KeywordIndex]; } + [[nodiscard]] + constexpr std::ptrdiff_t index() const noexcept + { + return m_KeywordIndex; + } + [[nodiscard]] bool operator==(keyword const&) const = default; @@ -166,6 +183,12 @@ namespace mimicpp::printing::type::lexing return textCollection[m_TextIndex]; } + [[nodiscard]] + constexpr std::ptrdiff_t index() const noexcept + { + return m_TextIndex; + } + [[nodiscard]] bool operator==(operator_or_punctuator const&) const = default; @@ -181,6 +204,14 @@ namespace mimicpp::printing::type::lexing bool operator==(identifier const&) const = default; }; + struct literal + { + StringViewT content; + + [[nodiscard]] + bool operator==(literal const&) const = default; + }; + struct end { [[nodiscard]] @@ -192,7 +223,8 @@ namespace mimicpp::printing::type::lexing space, keyword, operator_or_punctuator, - identifier>; + identifier, + literal>; struct token { @@ -261,6 +293,13 @@ namespace mimicpp::printing::type::lexing return next_as_op_or_punctuator(options); } + if (std::optional literal = try_next_as_literal()) + { + return token{ + .content = literal->content, + .classification = *std::move(literal)}; + } + 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 (auto const iter = util::binary_find(keyword::textCollection, content); @@ -359,6 +398,35 @@ namespace mimicpp::printing::type::lexing return content; } + + // This function currently implements just a very basic integer-literal detection. + // see: https://eel.is/c++draft/lex.literal.kinds#nt:literal + [[nodiscard]] + constexpr std::optional try_next_as_literal() noexcept + { + MIMICPP_ASSERT(!m_Text.empty(), "Empty text."); + + if (is_digit(m_Text.front())) + { + if ('0' == m_Text.front() + && 2u <= m_Text.size()) + { + // Todo: one of + // https://eel.is/c++draft/lex.icon#nt:binary-literal + // https://eel.is/c++draft/lex.icon#nt:octal-literal + // https://eel.is/c++draft/lex.icon#nt:hexadecimal-prefix + } + + // Todo: this is just a very naive approach. + auto const last = std::ranges::find_if_not(m_Text.cbegin() + 1, m_Text.cend(), is_digit); + StringViewT const content{m_Text.cbegin(), last}; + m_Text = {last, m_Text.cend()}; + + return literal{.content = content}; + } + + return std::nullopt; + } }; } diff --git a/include/mimic++/printing/type/NameParser.hpp b/include/mimic++/printing/type/NameParser.hpp deleted file mode 100644 index 5e3da204af..0000000000 --- a/include/mimic++/printing/type/NameParser.hpp +++ /dev/null @@ -1,620 +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_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++/printing/type/NameParserReductions.hpp" -#include "mimic++/printing/type/NameParserTokens.hpp" -#include "mimic++/utilities/C++23Backports.hpp" - -#ifndef MIMICPP_DETAIL_IS_MODULE - #include - #include - #include - #include - #include -#endif - -namespace mimicpp::printing::type::parsing -{ - template - class NameParser - { - public: - [[nodiscard]] - explicit constexpr NameParser(Visitor visitor, StringViewT const& content) noexcept(std::is_nothrow_move_constructible_v) - : m_Visitor{std::move(visitor)}, - m_Content{content}, - m_Lexer{content} - { - } - - constexpr void parse_type() - { - parse(); - token::try_reduce_as_type(m_TokenStack); - if (!finalize()) - { - emit_unrecognized(); - } - } - - constexpr void parse_function() - { - parse(); - - if (m_HasConversionOperator) - { - token::reduce_as_conversion_operator_function_identifier(m_TokenStack); - } - else - { - is_suffix_of(m_TokenStack) - || token::try_reduce_as_function_identifier(m_TokenStack); - } - - 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(); - } - } - } - - 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::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::operator_or_punctuator tilde{"~"}; - static constexpr lexing::keyword operatorKeyword{"operator"}; - 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"}; - static constexpr lexing::keyword classKeyword{"class"}; - 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{"__int64"}, - lexing::keyword{"long"}, - lexing::keyword{"short"}, - lexing::keyword{"signed"}, - lexing::keyword{"unsigned"}}; - - Visitor m_Visitor; - StringViewT m_Content; - lexing::NameLexer m_Lexer; - bool m_HasConversionOperator{false}; - - 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(); - !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() - { - if (1u == m_TokenStack.size()) - { - if (auto const* const end = std::get_if(&m_TokenStack.back())) - { - auto& unwrapped = unwrap_visitor(m_Visitor); - - unwrapped.begin(); - std::invoke(*end, 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(); - } - - [[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 (id->is_builtin() - && merge_with_next_token()) - { - auto& curContent = std::get(id->content); - auto const [nextContent, _] = m_Lexer.next(); - // Merge both keywords by simply treating them as contiguous content. - 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); - } - - // In certain cases, a space after an identifier has semantic significance. - // For example, consider the type names `void ()` and `foo()`: - // - `void ()` represents a function type returning `void`. - // - `foo()` represents a function named `foo`. - if (auto const* const nextOp = peek_if(); - nextOp - && util::contains(std::array{openingAngle, openingParens, openingCurly, singleQuote, backtick}, *nextOp)) - { - m_TokenStack.emplace_back(token::Space{}); - } - } - - 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([[maybe_unused]] StringViewT const content, lexing::keyword const& keyword) - { - if (constKeyword == keyword) - { - 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) - { - 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.isVolatile, "Specs is already volatile."); - top.isVolatile = true; - } - else if (noexceptKeyword == keyword) - { - 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()) - { - // 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; - } - else if (constexpr std::array collection{classKeyword, structKeyword, enumKeyword}; - util::contains(collection, keyword)) - { - // This token is needed, so we do not accidentally treat e.g. `(anonymous class)` as function args, - // because otherwise there would just be the `anonymous` identifier left. - m_TokenStack.emplace_back(token::TypeContext{.content = content}); - } - else if (util::contains(typeKeywordCollection, keyword)) - { - m_TokenStack.emplace_back( - token::Identifier{ - .isBuiltinType = true, - .content = content}); - } - } - - constexpr bool process_simple_operator() - { - auto dropSpaceInput = [this] { - if (std::holds_alternative(m_Lexer.peek().classification)) - { - std::ignore = m_Lexer.next(); - } - }; - - dropSpaceInput(); - - // As we assume valid input, we do not have to check for the actual symbol. - if (auto const next = m_Lexer.peek(); - auto const* operatorToken = std::get_if(&next.classification)) - { - std::ignore = m_Lexer.next(); - - auto const finishMultiOpOperator = [&, this]([[maybe_unused]] lexing::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); - } - // `operator <` and `operator <<` needs to be handled carefully, as it may come in as a template: - // `operator<<>` is actually `operator< <>`. - // Note: No tested c++ compiler actually allows `operator<<>`, but some environments still procude this. - else if (leftShift == *operatorToken) - { - dropSpaceInput(); - - if (auto const* const nextOp = peek_if(); - nextOp - // When next token starts a function or template, we know it's actually `operator <<`. - && (openingParens == *nextOp || openingAngle == *nextOp)) - { - m_TokenStack.emplace_back( - token::Identifier{ - .content = token::Identifier::OperatorInfo{.symbol = next.content}}); - } - // looks like an `operator< <>`, so just treat both `<` separately. - else - { - m_TokenStack.emplace_back( - token::Identifier{ - .content = token::Identifier::OperatorInfo{.symbol = next.content.substr(0u, 1u)}}); - handle_lexer_token(next.content.substr(1u, 1u), openingAngle); - } - } - else - { - m_TokenStack.emplace_back( - token::Identifier{ - .content = token::Identifier::OperatorInfo{.symbol = next.content}}); - } - - dropSpaceInput(); - - return true; - } - else if (auto const* keywordToken = std::get_if(&next.classification); - keywordToken - && util::contains(std::array{newKeyword, deleteKeyword, coAwaitKeyword}, *keywordToken)) - { - std::ignore = m_Lexer.next(); - - StringViewT content = next.content; - - if (newKeyword == *keywordToken || deleteKeyword == *keywordToken) - { - dropSpaceInput(); - - if (auto const* const opAfter = peek_if(); - opAfter - && openingSquare == *opAfter) - { - // Strip `[]` or `[ ]` from the input. - std::ignore = m_Lexer.next(); - dropSpaceInput(); - auto const closing = m_Lexer.next(); - 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; - } - - return false; - } - - constexpr void handle_lexer_token(StringViewT const content, lexing::operator_or_punctuator const& token) - { - if (scopeResolution == token) - { - token::try_reduce_as_function_identifier(m_TokenStack); - - m_TokenStack.emplace_back( - std::in_place_type, - content); - token::try_reduce_as_scope_sequence(m_TokenStack); - } - else if (commaSeparator == token) - { - if (is_suffix_of(m_TokenStack) - || token::try_reduce_as_type(m_TokenStack)) - { - token::try_reduce_as_arg_sequence(m_TokenStack); - } - - m_TokenStack.emplace_back( - std::in_place_type, - content); - } - else if (lvalueRef == token) - { - auto& specs = token::get_or_emplace_specs(m_TokenStack); - MIMICPP_ASSERT(token::Specs::Refness::none == specs.refness, "Specs already is a reference."); - specs.refness = token::Specs::Refness::lvalue; - } - else if (rvalueRef == token) - { - auto& specs = token::get_or_emplace_specs(m_TokenStack); - MIMICPP_ASSERT(token::Specs::Refness::none == specs.refness, "Specs already is a reference."); - specs.refness = token::Specs::Refness::rvalue; - } - else if (pointer == token) - { - auto& specs = token::get_or_emplace_specs(m_TokenStack); - specs.layers.emplace_back(); - } - else if (openingAngle == token) - { - m_TokenStack.emplace_back( - std::in_place_type, - content); - } - else if (closingAngle == token) - { - if (is_suffix_of(m_TokenStack) - || token::try_reduce_as_type(m_TokenStack)) - { - token::try_reduce_as_arg_sequence(m_TokenStack); - } - - m_TokenStack.emplace_back( - std::in_place_type, - content); - token::try_reduce_as_template_identifier(m_TokenStack) - || token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); - } - else if (openingParens == token) - { - m_TokenStack.emplace_back( - std::in_place_type, - content); - } - else if (closingParens == token) - { - bool isNextOpeningParens{false}; - if (auto const* const nextOp = peek_if()) - { - isNextOpeningParens = (openingParens == *nextOp); - } - - // There can be no `(` directly after function-args, thus do not perform any reduction if such a token is found. - // This helps when function-ptrs are given, so that we do not accidentally reduce something like `(__cdecl*)` as function-args. - if (!isNextOpeningParens) - { - if (is_suffix_of(m_TokenStack) - || token::try_reduce_as_type(m_TokenStack)) - { - token::try_reduce_as_arg_sequence(m_TokenStack); - } - } - - m_TokenStack.emplace_back( - std::in_place_type, - content); - - if (bool const result = isNextOpeningParens - ? token::try_reduce_as_function_ptr(m_TokenStack) - : token::try_reduce_as_function_context(m_TokenStack); - !result) - { - token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); - } - } - else if (openingCurly == token) - { - m_TokenStack.emplace_back( - std::in_place_type, - content); - } - else if (closingCurly == token) - { - m_TokenStack.emplace_back( - std::in_place_type, - content); - token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); - } - else if (backtick == token) - { - m_TokenStack.emplace_back( - std::in_place_type, - content); - } - else if (singleQuote == token) - { - if (token::try_reduce_as_function_identifier(m_TokenStack)) - { - unwrap_msvc_like_function(); - } - // Something like `id1::id2' should become id1::id2, so just remove the leading backtick. - else if (is_suffix_of(m_TokenStack)) - { - m_TokenStack.erase(m_TokenStack.cend() - 3u); - } - else - { - m_TokenStack.emplace_back( - std::in_place_type, - content); - // Well, some environments wrap in `' (like msvc) and some wrap in '' (libc++). - token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack) - || token::try_reduce_as_placeholder_identifier_wrapped(m_TokenStack); - } - } - // The current parsing process will never receive an `<<` or `>>` without a preceding `operator` keyword. - // As the current `operator` parsing currently consumes the next op-symbol, we will never reach this point - // with an actual left or right-shift. So, to make that easier, just split them. - else if (leftShift == token) - { - handle_lexer_token(content.substr(0, 1u), openingAngle); - handle_lexer_token(content.substr(1u, 1u), openingAngle); - } - else if (rightShift == token) - { - handle_lexer_token(content.substr(0, 1u), closingAngle); - handle_lexer_token(content.substr(1u, 1u), closingAngle); - } - // A `~` without a preceding `operator` keyword must be followed by a type-name and forms a destructor. - else if (tilde == token) - { - if (auto const* nextId = peek_if()) - { - StringViewT const merged{ - content.data(), - nextId->content.data() + nextId->content.size()}; - m_TokenStack.emplace_back(token::Identifier{.content = merged}); - std::ignore = m_Lexer.next(); - } - } - // 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() - { - MIMICPP_ASSERT(is_suffix_of(m_TokenStack), "Invalid state."); - - auto funIdentifier = std::get(m_TokenStack.back()); - m_TokenStack.pop_back(); - - std::optional scopes{}; - if (auto* const scopeSeq = match_suffix(m_TokenStack)) - { - scopes = std::move(*scopeSeq); - m_TokenStack.pop_back(); - } - - // Ignore return-types. - if (is_suffix_of(m_TokenStack)) - { - m_TokenStack.pop_back(); - } - - 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(); - } - - MIMICPP_ASSERT(!is_suffix_of(m_TokenStack), "Invlid state."); - if (scopes) - { - m_TokenStack.emplace_back(*std::move(scopes)); - } - - m_TokenStack.emplace_back(std::move(funIdentifier)); - } - }; -} - -#endif diff --git a/include/mimic++/printing/type/NameParserReductions.hpp b/include/mimic++/printing/type/NameParserReductions.hpp deleted file mode 100644 index 19372bb8b3..0000000000 --- a/include/mimic++/printing/type/NameParserReductions.hpp +++ /dev/null @@ -1,755 +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_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" - -#ifndef MIMICPP_DETAIL_IS_MODULE - #include - #include - #include - #include -#endif - -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); - } - - constexpr void ignore_space(std::span& tokenStack) noexcept - { - if (is_suffix_of(tokenStack)) - { - remove_suffix(tokenStack, 1u); - } - } - - constexpr void ignore_reserved_identifier(std::span& tokenStack) noexcept - { - if (auto const* const id = match_suffix(tokenStack); - id - && id->is_reserved()) - { - remove_suffix(tokenStack, 1u); - } - } - - namespace token - { - bool try_reduce_as_type(TokenStack& tokenStack); - - inline bool try_reduce_as_scope_sequence(TokenStack& tokenStack) - { - std::span pendingTokens{tokenStack}; - if (!is_suffix_of(pendingTokens)) - { - return false; - } - remove_suffix(pendingTokens, 1u); - - ScopeSequence::Scope scope{}; - if (auto* identifier = match_suffix(pendingTokens)) - { - scope = std::move(*identifier); - } - else if (auto* funIdentifier = match_suffix(pendingTokens)) - { - scope = std::move(*funIdentifier); - } - else - { - return false; - } - - remove_suffix(pendingTokens, 1u); - tokenStack.resize(pendingTokens.size()); - - if (auto* sequence = match_suffix(tokenStack)) - { - sequence->scopes.emplace_back(std::move(scope)); - } - else - { - tokenStack.emplace_back( - ScopeSequence{ - .scopes = {std::move(scope)}}); - } - - return true; - } - - MIMICPP_DETAIL_CONSTEXPR_VECTOR bool try_reduce_as_arg_sequence(TokenStack& tokenStack) - { - std::span pendingTokens{tokenStack}; - if (std::optional suffix = match_suffix(pendingTokens)) - { - // Keep ArgSequence - remove_suffix(pendingTokens, 2u); - auto& [seq, sep, type] = *suffix; - - seq.types.emplace_back(std::move(type)); - tokenStack.resize(pendingTokens.size()); - - return true; - } - - if (auto* type = match_suffix(pendingTokens)) - { - remove_suffix(pendingTokens, 1u); - - ArgSequence seq{}; - seq.types.emplace_back(std::move(*type)); - tokenStack.resize(pendingTokens.size()); - tokenStack.emplace_back(std::move(seq)); - - return true; - } - - return false; - } - - constexpr bool try_reduce_as_template_identifier(TokenStack& tokenStack) - { - std::span pendingTokens{tokenStack}; - if (!is_suffix_of(pendingTokens)) - { - return false; - } - remove_suffix(pendingTokens, 1u); - - auto* args = match_suffix(pendingTokens); - if (args) - { - remove_suffix(pendingTokens, 1u); - } - - if (!is_suffix_of(pendingTokens)) - { - return false; - } - remove_suffix(pendingTokens, 1u); - - auto* id = match_suffix(pendingTokens); - if (!id - || id->is_template()) - { - return false; - } - - if (args) - { - id->templateArgs = std::move(*args); - } - else - { - id->templateArgs.emplace(); - } - tokenStack.resize(pendingTokens.size()); - - return true; - } - - MIMICPP_DETAIL_CONSTEXPR_VECTOR bool try_reduce_as_function_context(TokenStack& tokenStack) - { - std::span pendingTokens{tokenStack}; - if (!is_suffix_of(pendingTokens)) - { - return false; - } - remove_suffix(pendingTokens, 1u); - - auto* args = match_suffix(pendingTokens); - if (args) - { - remove_suffix(pendingTokens, 1u); - } - - if (!is_suffix_of(pendingTokens)) - { - return false; - } - remove_suffix(pendingTokens, 1u); - - // There can never be valid function-args in form of `::()`, thus reject it. - if (is_suffix_of(pendingTokens) - || is_suffix_of(pendingTokens)) - { - return false; - } - - FunctionContext funCtx{}; - if (args) - { - // We omit function args with only `void`. - if (1u != args->types.size() - || !args->types.front().is_void()) - { - funCtx.args = std::move(*args); - } - } - - tokenStack.resize(pendingTokens.size()); - tokenStack.emplace_back(std::move(funCtx)); - - return true; - } - - inline bool try_reduce_as_function_identifier(TokenStack& tokenStack) - { - std::span pendingStack{tokenStack}; - - // There may be a space, when the function is wrapped inside single-quotes. - ignore_space(pendingStack); - - // Ignore something like `__ptr64` on msvc. - ignore_reserved_identifier(pendingStack); - - if (std::optional suffix = match_suffix(pendingStack)) - { - remove_suffix(pendingStack, 2u); - - auto& [identifier, funCtx] = *suffix; - FunctionIdentifier funIdentifier{ - .identifier = std::move(identifier), - .context = std::move(funCtx)}; - - tokenStack.resize(pendingStack.size() + 1u); - tokenStack.back() = std::move(funIdentifier); - - return true; - } - - return false; - } - - [[nodiscard]] - constexpr bool is_identifier_prefix(std::span const tokenStack) noexcept - { - return tokenStack.empty() - || is_suffix_of(tokenStack) - || is_suffix_of(tokenStack) - || is_suffix_of(tokenStack) - || is_suffix_of(tokenStack) - || is_suffix_of(tokenStack) - || is_suffix_of(tokenStack) - || is_suffix_of(tokenStack) - || is_suffix_of(tokenStack); - } - - template - constexpr bool try_reduce_as_placeholder_identifier_wrapped(TokenStack& tokenStack) - { - 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.rbegin(), - pendingTokens.rend(), - [](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}; - - // There may be a space in front of the placeholder, which isn't necessary. - ignore_space(pendingTokens); - - tokenStack.resize(pendingTokens.size() + 1u); - tokenStack.back() = Identifier{.content = content}; - - return true; - } - - inline bool try_reduce_as_function_type(TokenStack& tokenStack) - { - std::span pendingTokens{tokenStack}; - - auto* const ctx = match_suffix(pendingTokens); - if (!ctx) - { - return false; - } - remove_suffix(pendingTokens, 1u); - - // The return type is always delimited by space from the arg-list. - if (!is_suffix_of(pendingTokens)) - { - // Well, of course there is an exception to the "always". - // There is that case on msvc, where it does not add that space between the function-args and the call-convention. - // E.g. `void __cdecl()`. - // But, as we can be pretty sure from the context, that the identifier can never be the function name, accept it - // as valid delimiter. - if (auto const* const id = match_suffix(pendingTokens); - !id - || !id->is_reserved()) - { - return false; - } - } - remove_suffix(pendingTokens, 1u); - - // Ignore call-convention. - ignore_reserved_identifier(pendingTokens); - - auto* const returnType = match_suffix(pendingTokens); - if (!returnType) - { - return false; - } - remove_suffix(pendingTokens, 1u); - - FunctionType funType{ - .returnType = std::make_shared(std::move(*returnType)), - .context = std::move(*ctx)}; - - tokenStack.resize( - std::exchange(pendingTokens, {}).size() + 1u); - tokenStack.back().emplace(std::move(funType)); - - return true; - } - - inline bool try_reduce_as_function_ptr(TokenStack& tokenStack) - { - std::span pendingTokens{tokenStack}; - if (!is_suffix_of(pendingTokens)) - { - return false; - } - remove_suffix(pendingTokens, 1u); - - auto* nestedFunCtx = match_suffix(pendingTokens); - FunctionPtr* nestedFunPtr{}; - if (nestedFunCtx) - { - remove_suffix(pendingTokens, 1u); - - if (auto* ptr = match_suffix(pendingTokens)) - { - nestedFunPtr = ptr; - remove_suffix(pendingTokens, 1u); - } - } - - ignore_space(pendingTokens); - // Ignore call-convention. - ignore_reserved_identifier(pendingTokens); - - auto* specs = match_suffix(pendingTokens); - ScopeSequence* scopeSeq{}; - if (specs && specs->has_ptr()) - { - remove_suffix(pendingTokens, 1u); - - if (auto* const seq = match_suffix(pendingTokens)) - { - scopeSeq = seq; - remove_suffix(pendingTokens, 1u); - } - - // Ignore call-convention, which may have already been reduced to a type. - if (auto const* const type = match_suffix(pendingTokens)) - { - if (auto const* const regular = std::get_if(&type->state); - regular - && regular->identifier.is_reserved()) - { - remove_suffix(pendingTokens, 1u); - } - } - } - else - { - RegularType* regular{}; - if (auto* const type = match_suffix(pendingTokens)) - { - regular = std::get_if(&type->state); - } - - // Unfortunately msvc produces something like `(__cdecl*)` for the function-ptr part. - // There is no way to reliably detect whether denotes a function-ptr or argument-list. - // So we have to make sure, that the reduction is only called in the right places. - // Then we can extract the info from that type. - if (!regular - || !regular->identifier.is_reserved() - || !regular->specs.has_ptr()) - { - return false; - } - - specs = ®ular->specs; - remove_suffix(pendingTokens, 1u); - } - - if (!is_suffix_of(pendingTokens)) - { - return false; - } - remove_suffix(pendingTokens, 1u); - - FunctionPtr funPtr{.specs = std::move(*specs)}; - if (scopeSeq) - { - funPtr.scopes = std::move(*scopeSeq); - } - - if (nestedFunCtx) - { - FunctionPtr::NestedInfo nested{ - .ctx = std::move(*nestedFunCtx)}; - - if (nestedFunPtr) - { - nested.ptr = std::make_shared(std::move(*nestedFunPtr)); - } - - funPtr.nested = std::move(nested); - } - - tokenStack.resize(pendingTokens.size()); - tokenStack.emplace_back(std::move(funPtr)); - - return true; - } - - namespace detail - { - void handled_nested_function_ptr(TokenStack& tokenStack, FunctionPtr::NestedInfo info); - } - - inline bool try_reduce_as_function_ptr_type(TokenStack& tokenStack) - { - std::span pendingTokens{tokenStack}; - - // Ignore something like `__ptr64`. - ignore_reserved_identifier(pendingTokens); - - // The return type is always delimited by space from the spec part. - if (std::optional suffix = match_suffix(pendingTokens)) - { - remove_suffix(pendingTokens, 4u); - auto& [returnType, space, ptr, ctx] = *suffix; - - std::optional nestedInfo = std::move(ptr.nested); - FunctionPtrType ptrType{ - .returnType = std::make_shared(std::move(returnType)), - .scopes = std::move(ptr.scopes), - .specs = std::move(ptr.specs), - .context = std::move(ctx)}; - - tokenStack.resize( - std::exchange(pendingTokens, {}).size() + 1u); - tokenStack.back().emplace(std::move(ptrType)); - - // We got something like `ret (*(outer-args))(args)` or `ret (*(*)(outer-args))(args)`, where the currently - // processed function-ptr is actually the return-type of the inner function(-ptr). - // This may nested in an arbitrary depth! - if (nestedInfo) - { - detail::handled_nested_function_ptr(tokenStack, *std::move(nestedInfo)); - } - - return true; - } - - return false; - } - - namespace detail - { - inline void handled_nested_function_ptr(TokenStack& tokenStack, FunctionPtr::NestedInfo info) - { - auto& [ptr, ctx] = info; - - // We need to insert an extra space, to follow the general syntax constraints. - tokenStack.emplace_back(Space{}); - - bool const isFunPtr{ptr}; - if (ptr) - { - tokenStack.emplace_back(std::move(*ptr)); - } - - tokenStack.emplace_back(std::move(ctx)); - - if (isFunPtr) - { - try_reduce_as_function_ptr_type(tokenStack); - } - else - { - try_reduce_as_function_type(tokenStack); - } - } - } - - inline bool try_reduce_as_regular_type(TokenStack& tokenStack) - { - std::span pendingTokens{tokenStack}; - auto* const identifier = match_suffix(pendingTokens); - if (!identifier) - { - return false; - } - remove_suffix(pendingTokens, 1u); - - // There may be the case, where we already reduced a Type but additionally got something like `__ptr64`. - // E.g. `int& __ptr64`. Remove that trailing identifier and treat it as a successful reduction. - if (identifier->is_reserved() - && is_suffix_of(pendingTokens)) - { - tokenStack.pop_back(); - - return true; - } - - auto* const scopes = match_suffix(pendingTokens); - if (scopes) - { - remove_suffix(pendingTokens, 1u); - } - - auto* const prefixSpecs = match_suffix(pendingTokens); - if (prefixSpecs) - { - // Prefix-specs can only have `const` and/or `volatile`. - if (auto const& layers = prefixSpecs->layers; - token::Specs::Refness::none != prefixSpecs->refness - || prefixSpecs->isNoexcept - || 1u != layers.size()) - { - return false; - } - - remove_suffix(pendingTokens, 1u); - } - - // We do never allow two or more adjacent `Type` tokens, as there is literally no case where this would make sense. - if (is_suffix_of(pendingTokens) - || is_suffix_of(pendingTokens)) - { - return false; - } - - RegularType newType{.identifier = std::move(*identifier)}; - if (prefixSpecs) - { - newType.specs = std::move(*prefixSpecs); - } - - if (scopes) - { - newType.scopes = std::move(*scopes); - } - - // Ignore something like `class` or `struct` directly in front of a type. - if (is_suffix_of(pendingTokens)) - { - remove_suffix(pendingTokens, 1u); - } - - tokenStack.resize(pendingTokens.size()); - tokenStack.emplace_back( - std::in_place_type, - std::move(newType)); - - return true; - } - - inline bool try_reduce_as_type(TokenStack& tokenStack) - { - return try_reduce_as_function_ptr_type(tokenStack) - || try_reduce_as_function_type(tokenStack) - || try_reduce_as_regular_type(tokenStack); - } - - inline bool try_reduce_as_function(TokenStack& tokenStack) - { - std::span pendingTokens{tokenStack}; - if (auto* funIdentifier = match_suffix(pendingTokens)) - { - Function function{ - .identifier = std::move(*funIdentifier)}; - remove_suffix(pendingTokens, 1u); - - if (auto* scopes = match_suffix(pendingTokens)) - { - function.scopes = std::move(*scopes); - remove_suffix(pendingTokens, 1u); - } - - // Ignore call-convention. - ignore_reserved_identifier(pendingTokens); - - if (auto* returnType = match_suffix(pendingTokens)) - { - function.returnType = std::make_shared(std::move(*returnType)); - remove_suffix(pendingTokens, 1u); - } - - tokenStack.resize( - std::exchange(pendingTokens, {}).size()); - tokenStack.emplace_back(std::move(function)); - - return true; - } - - return false; - } - - inline void reduce_as_conversion_operator_function_identifier(TokenStack& tokenStack) - { - // Functions reported by stacktrace are sometimes not in actual function form, - // so we need to be more permissive here. - std::optional funCtx{}; - if (auto* const ctx = match_suffix(tokenStack)) - { - funCtx = std::move(*ctx); - tokenStack.pop_back(); - } - - try_reduce_as_type(tokenStack); - 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)}}; - - if (funCtx) - { - tokenStack.emplace_back(*std::move(funCtx)); - try_reduce_as_function_identifier(tokenStack); - } - } - - [[nodiscard]] - constexpr Specs& get_or_emplace_specs(TokenStack& tokenStack) - { - // Maybe wo got something like `type&` and need to reduce that identifier to an actual `Type` token. - if (is_suffix_of(tokenStack)) - { - if (try_reduce_as_type(tokenStack)) - { - return std::get(tokenStack.back()).specs(); - } - - // The reduction failed, so it's something like `__ptr64` and should be ignored. - // This may happen, when specs are attached to functions, like `ret T::foo() & __ptr64`. - MIMICPP_ASSERT(std::get(tokenStack.back()).is_reserved(), "Unexpected token."); - tokenStack.pop_back(); - } - - if (auto* const type = match_suffix(tokenStack)) - { - return type->specs(); - } - - if (auto* const ctx = match_suffix(tokenStack)) - { - return ctx->specs; - } - - if (auto* specs = match_suffix(tokenStack)) - { - return *specs; - } - - // No specs found yet? Assume prefix specs. - return std::get(tokenStack.emplace_back(Specs{})); - } - } -} - -#endif diff --git a/include/mimic++/printing/type/NameParserTokens.hpp b/include/mimic++/printing/type/NameParserTokens.hpp deleted file mode 100644 index 3e2ec2620b..0000000000 --- a/include/mimic++/printing/type/NameParserTokens.hpp +++ /dev/null @@ -1,681 +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_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" - -#ifndef MIMICPP_DETAIL_IS_MODULE - #include - #include - #include - #include - #include - #include - #include - #include -#endif - -namespace mimicpp::printing::type::parsing -{ - template - concept parser_visitor = std::movable - && requires(std::unwrap_reference_t visitor, StringViewT content, std::ptrdiff_t count) { - visitor.unrecognized(content); - - visitor.begin(); - visitor.end(); - - visitor.begin_type(); - visitor.end_type(); - - visitor.begin_scope(); - visitor.end_scope(); - - visitor.add_identifier(content); - visitor.add_arg(); - - visitor.begin_template_args(count); - visitor.end_template_args(); - - visitor.add_const(); - visitor.add_volatile(); - visitor.add_noexcept(); - visitor.add_ptr(); - visitor.add_lvalue_ref(); - visitor.add_rvalue_ref(); - - visitor.begin_function(); - visitor.end_function(); - visitor.begin_return_type(); - visitor.end_return_type(); - visitor.begin_function_args(count); - visitor.end_function_args(); - - visitor.begin_function_ptr(); - visitor.end_function_ptr(); - - visitor.begin_operator_identifier(); - visitor.end_operator_identifier(); - }; - - template - [[nodiscard]] - constexpr auto& unwrap_visitor(Visitor& visitor) noexcept - { - return static_cast< - std::add_lvalue_reference_t< - std::unwrap_reference_t>>(visitor); - } -} - -namespace mimicpp::printing::type::parsing::token -{ - class Type; - - class Space - { - }; - - class OperatorKeyword - { - }; - - class ScopeResolution - { - public: - StringViewT content; - }; - - 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}; - - 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]] - MIMICPP_DETAIL_CONSTEXPR_VECTOR 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; - - MIMICPP_DETAIL_CONSTEXPR_VECTOR ~ArgSequence() noexcept; - MIMICPP_DETAIL_CONSTEXPR_VECTOR ArgSequence(); - MIMICPP_DETAIL_CONSTEXPR_VECTOR ArgSequence(ArgSequence const&); - MIMICPP_DETAIL_CONSTEXPR_VECTOR ArgSequence& operator=(ArgSequence const&); - MIMICPP_DETAIL_CONSTEXPR_VECTOR ArgSequence(ArgSequence&&) noexcept; - MIMICPP_DETAIL_CONSTEXPR_VECTOR ArgSequence& operator=(ArgSequence&&) noexcept; - - template - constexpr void operator()(Visitor& visitor) const; - - template - constexpr void handle_as_template_args(Visitor& visitor) const; - }; - - class Identifier - { - public: - bool isBuiltinType{false}; - - struct OperatorInfo - { - using Symbol = std::variant>; - Symbol symbol{}; - }; - - using Content = std::variant; - Content content{}; - std::optional templateArgs{}; - - [[nodiscard]] - constexpr bool is_template() const noexcept - { - return templateArgs.has_value(); - } - - [[nodiscard]] - constexpr bool is_void() const noexcept - { - auto const* const id = std::get_if(&content); - - return id - && "void" == *id; - } - - [[nodiscard]] - constexpr bool is_reserved() const noexcept - { - auto const* const id = std::get_if(&content); - - return id - && id->starts_with("__"); - } - - [[nodiscard]] - constexpr bool is_builtin() const noexcept - { - return isBuiltinType; - } - - template - constexpr void operator()(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - std::visit( - [&](auto const& inner) { handle_content(unwrapped, inner); }, - content); - - if (templateArgs) - { - templateArgs->handle_as_template_args(unwrapped); - } - } - - public: - template - static constexpr void handle_content(Visitor& visitor, 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: - ArgSequence args{}; - Specs specs{}; - - template - constexpr void operator()(Visitor& visitor) const; - }; - - class FunctionIdentifier - { - public: - Identifier identifier; - FunctionContext context{}; - - template - constexpr void operator()(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - std::invoke(identifier, unwrapped); - std::invoke(context, unwrapped); - } - }; - - class ScopeSequence - { - public: - using Scope = std::variant; - std::vector scopes{}; - - template - constexpr void operator()(Visitor& visitor) const - { - 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); - - unwrapped.begin_type(); - - if (scopes) - { - std::invoke(*scopes, unwrapped); - } - - std::invoke(identifier, unwrapped); - std::invoke(specs, unwrapped); - - unwrapped.end_type(); - } - }; - - class FunctionType - { - public: - std::shared_ptr returnType{}; - FunctionContext context{}; - - template - void operator()(Visitor& visitor) const - { - 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(); - } - }; - - 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_type(); - - unwrapped.begin_return_type(); - std::invoke(*returnType, visitor); - unwrapped.end_return_type(); - - unwrapped.begin_function_ptr(); - if (scopes) - { - std::invoke(*scopes, unwrapped); - } - - std::invoke(specs, unwrapped); - unwrapped.end_function_ptr(); - - std::invoke(context, unwrapped); - - unwrapped.end_type(); - } - }; - - class Type - { - public: - using State = std::variant; - State state; - - [[nodiscard]] - constexpr bool is_void() const noexcept - { - auto const* const regularType = std::get_if(&state); - - return regularType - && regularType->identifier.is_void(); - } - - [[nodiscard]] - constexpr Specs& specs() noexcept - { - return std::visit( - [&](auto& inner) noexcept -> Specs& { return specs(inner); }, - state); - } - - template - void operator()(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - std::visit( - [&](auto const& inner) { std::invoke(inner, unwrapped); }, - state); - } - - private: - [[nodiscard]] - static constexpr Specs& specs(RegularType& type) noexcept - { - return type.specs; - } - - [[nodiscard]] - static constexpr Specs& specs(FunctionType& type) noexcept - { - return type.context.specs; - } - - [[nodiscard]] - static constexpr Specs& specs(FunctionPtrType& type) noexcept - { - return type.specs; - } - }; - - MIMICPP_DETAIL_CONSTEXPR_VECTOR ArgSequence::~ArgSequence() noexcept = default; - MIMICPP_DETAIL_CONSTEXPR_VECTOR ArgSequence::ArgSequence() = default; - MIMICPP_DETAIL_CONSTEXPR_VECTOR ArgSequence::ArgSequence(ArgSequence const&) = default; - MIMICPP_DETAIL_CONSTEXPR_VECTOR ArgSequence& ArgSequence::operator=(ArgSequence const&) = default; - MIMICPP_DETAIL_CONSTEXPR_VECTOR ArgSequence::ArgSequence(ArgSequence&&) noexcept = default; - MIMICPP_DETAIL_CONSTEXPR_VECTOR ArgSequence& ArgSequence::operator=(ArgSequence&&) noexcept = default; - - template - constexpr void ArgSequence::operator()(Visitor& visitor) const - { - if (!types.empty()) - { - auto& unwrapped = unwrap_visitor(visitor); - - std::invoke(types.front(), unwrapped); - - for (auto const& type : types | std::views::drop(1)) - { - unwrapped.add_arg(); - std::invoke(type, unwrapped); - } - } - } - - template - constexpr void ArgSequence::handle_as_template_args(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - unwrapped.begin_template_args(std::ranges::ssize(types)); - std::invoke(*this, unwrapped); - unwrapped.end_template_args(); - } - - template - constexpr void FunctionContext::operator()(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - unwrapped.begin_function_args(std::ranges::ssize(args.types)); - std::invoke(args, unwrapped); - unwrapped.end_function_args(); - std::invoke(specs, unwrapped); - } - - class Function - { - public: - std::shared_ptr returnType{}; - std::optional scopes{}; - FunctionIdentifier identifier{}; - - template - void operator()(Visitor& visitor) const - { - auto& unwrapped = unwrap_visitor(visitor); - - unwrapped.begin_function(); - - if (returnType) - { - unwrapped.begin_return_type(); - std::invoke(*returnType, visitor); - unwrapped.end_return_type(); - } - - if (scopes) - { - std::invoke(*scopes, unwrapped); - } - - std::invoke(identifier, unwrapped); - - unwrapped.end_function(); - } - }; -} - -namespace mimicpp::printing::type::parsing -{ - using Token = std::variant< - token::Space, - token::OperatorKeyword, - token::ScopeResolution, - token::ArgSeparator, - token::OpeningAngle, - token::ClosingAngle, - token::OpeningParens, - token::ClosingParens, - token::OpeningCurly, - token::ClosingCurly, - token::OpeningBacktick, - token::ClosingSingleQuote, - token::TypeContext, - - token::Identifier, - token::FunctionIdentifier, - token::ScopeSequence, - token::ArgSequence, - token::FunctionContext, - token::FunctionPtr, - token::Specs, - token::Type, - token::Function>; - using TokenStack = std::vector; - - template - concept token_type = requires(Token const& token) { - { std::holds_alternative(token) } -> util::boolean_testable; - }; -} - -#endif diff --git a/include/mimic++/printing/type/NamePrintVisitor.hpp b/include/mimic++/printing/type/NamePrintVisitor.hpp deleted file mode 100644 index 3eb97c28d0..0000000000 --- a/include/mimic++/printing/type/NamePrintVisitor.hpp +++ /dev/null @@ -1,372 +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_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" - -#ifndef MIMICPP_DETAIL_IS_MODULE - #include - #include - #include - #include - #include - #include - #include -#endif - -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}"}, - {"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.crbegin(), content.crend(), ')'); - print("lambda"); - print(StringViewT{closingIter.base(), content.cend() - 1}); - - 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; - } - - // 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; - } - - if (content.starts_with('`') - && content.ends_with('\'')) - { - // 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; - } - - content = content.substr(1u, content.size() - 2u); - } - - 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([[maybe_unused]] std::ptrdiff_t const count) - { - 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([[maybe_unused]] std::ptrdiff_t const count) - { - 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 diff --git a/include/mimic++/printing/type/Parser.hpp b/include/mimic++/printing/type/Parser.hpp new file mode 100644 index 0000000000..1217ca0106 --- /dev/null +++ b/include/mimic++/printing/type/Parser.hpp @@ -0,0 +1,1392 @@ +// Copyright Dominic (DNKpp) Koepke 2026. +// 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_PARSER_HPP +#define MIMICPP_PRINTING_TYPE_PARSER_HPP + +#pragma once + +#include "mimic++/config/Config.hpp" +#include "mimic++/printing/type/Lexer.hpp" +#include "mimic++/printing/type/ParserState.hpp" +#include "mimic++/utilities/Algorithm.hpp" +#include "mimic++/utilities/C++23Backports.hpp" +#include "mimic++/utilities/PassKey.hpp" + +#ifndef MIMICPP_DETAIL_IS_MODULE + #include + #include + #include + #include + #include +#endif + +namespace mimicpp::printing::type::parsing::v2 +{ + class Transaction; + + class TokenStream + { + public: + [[nodiscard]] + explicit MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES TokenStream(lexing::NameLexer& lexer) noexcept + { + while (!std::holds_alternative(lexer.peek().classification)) + { + if (auto token = lexer.next(); + !std::holds_alternative(token.classification)) + { + m_Tokens.emplace_back(std::move(token)); + } + } + + m_Tokens.emplace_back(lexer.next()); + } + + [[nodiscard]] + constexpr bool is_eof() const noexcept + { + return m_Index == std::ranges::size(m_Tokens) - 1u; + } + + [[nodiscard]] + constexpr lexing::token const& peek() const + { + MIMICPP_ASSERT(m_Index < std::ranges::size(m_Tokens), "Stream is at end."); + return std::span{m_Tokens}[m_Index]; + } + + constexpr void consume() + { + MIMICPP_ASSERT(!is_eof(), "EOF cannot be consumed."); + ++m_Index; + } + + [[nodiscard]] + constexpr std::size_t pos() const noexcept + { + return m_Index; + } + + constexpr void seek(util::pass_key const /*key*/, std::size_t const pos) + { + m_Index = pos; + } + + private: + std::vector m_Tokens; + std::size_t m_Index{}; + }; + + class Transaction + { + public: + Transaction(Transaction const&) = delete; + Transaction& operator=(Transaction const&) = delete; + Transaction(Transaction&&) = delete; + Transaction& operator=(Transaction&&) = delete; + + constexpr ~Transaction() noexcept + { + if (is_active()) + { + m_Stream->seek(util::pass_key{}, m_Checkpoint); + } + } + + [[nodiscard]] + explicit constexpr Transaction(TokenStream& stream) noexcept + : m_Stream{std::addressof(stream)}, + m_Checkpoint{stream.pos()} + { + } + + constexpr void commit() noexcept + { + m_Stream = nullptr; + } + + [[nodiscard]] + constexpr bool is_active() const noexcept + { + return m_Stream != nullptr; + } + + private: + TokenStream* m_Stream; + std::size_t m_Checkpoint; + }; + + template + class StateGuard + { + public: + StateGuard(StateGuard const&) = delete; + StateGuard& operator=(StateGuard const&) = delete; + StateGuard(StateGuard&&) = delete; + StateGuard& operator=(StateGuard&&) = delete; + + ~StateGuard() = default; + + template + requires std::constructible_from + [[nodiscard]] + explicit constexpr StateGuard(TokenStream& stream, Args&&... args) + : m_Transaction{stream}, + m_State{std::in_place, std::forward(args)...} + { + } + + [[nodiscard]] + constexpr State& operator*() noexcept + { + MIMICPP_ASSERT(m_State, "State was already consumed."); + return *m_State; + } + + [[nodiscard]] + constexpr State* operator->() noexcept + { + MIMICPP_ASSERT(m_State, "State was already consumed."); + return &*m_State; + } + + [[nodiscard]] + constexpr std::optional take() && + { + MIMICPP_ASSERT(m_State, "State was already consumed."); + m_Transaction.commit(); + return std::exchange(m_State, std::nullopt); + } + + private: + Transaction m_Transaction; + std::optional m_State; + }; + + template + [[nodiscard]] + constexpr auto make_map(std::pair const (&mappings)[length]) + { + class Map + { + public: + [[nodiscard]] + explicit constexpr Map(std::pair const (&candidates)[length]) + : m_Candidates{std::to_array(candidates)} + { + } + + [[nodiscard]] + constexpr std::optional operator()(Key const& key) const + { + if (auto const iter = std::ranges::find(m_Candidates, key, &std::pair::first); + iter != std::ranges::end(m_Candidates)) + { + return iter->second; + } + + return std::nullopt; + } + + private: + std::array, length> m_Candidates; + }; + + return Map{mappings}; + } + + template + [[nodiscard]] + constexpr LexerTokenClass const* peek_if(TokenStream const& stream) noexcept + { + return std::get_if(&stream.peek().classification); + } + + template + [[nodiscard]] + constexpr std::optional expect(TokenStream& stream, LexerTokenClass const& expected) + { + if (auto const* const token = peek_if(stream); + token + && *token == expected) + { + std::optional result = *token; + stream.consume(); + + return result; + } + + return std::nullopt; + } + + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_parameters_and_qualifiers(TokenStream& stream); + + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_type_specifier_seq(TokenStream& stream); + + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_ptr_operator(TokenStream& stream); + + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_type_id(TokenStream& stream); + + // see: https://eel.is/c++draft/class.pre#nt:class-key + [[nodiscard]] + constexpr std::optional parse_class_key(TokenStream& stream) + { + if (auto const* const keyword = peek_if(stream)) + { + constexpr auto map = make_map({ + {lexing::keyword{"class"}, state::ClassKey::id_class }, + {lexing::keyword{"struct"}, state::ClassKey::id_struct} + // Todo: {lexing::keyword{"union"}, tokens::class_key::id_union} + }); + + if (std::optional const classKey = map(*keyword)) + { + stream.consume(); + return classKey; + } + } + + return std::nullopt; + } + + // see: https://eel.is/c++draft/dcl.enum#nt:enum-key + [[nodiscard]] + constexpr std::optional parse_enum_key(TokenStream& stream) + { + if (expect(stream, lexing::keyword{"enum"})) + { + if (expect(stream, lexing::keyword{"class"})) + { + return state::EnumKey::id_enum_class; + } + + if (expect(stream, lexing::keyword{"struct"})) + { + return state::EnumKey::id_enum_struct; + } + + return state::EnumKey::id_enum; + } + + return std::nullopt; + } + + [[nodiscard]] + constexpr std::optional parse_type_key(TokenStream& stream) + { + if (auto const key = parse_class_key(stream)) + { + return {*key}; + } + + if (auto const key = parse_enum_key(stream)) + { + return {*key}; + } + + return std::nullopt; + } + + // see: https://eel.is/c++draft/dcl.decl.general#nt:ref-qualifier + [[nodiscard]] + constexpr std::optional parse_ref_qualifier(TokenStream& stream) + { + if (auto const* const op = peek_if(stream)) + { + constexpr auto map = make_map({ + {lexing::operator_or_punctuator{"&"}, state::RefQualifier::id_ref }, + {lexing::operator_or_punctuator{"&&"}, state::RefQualifier::id_refref} + }); + + if (std::optional const qualifier = map(*op)) + { + stream.consume(); + return qualifier; + } + } + + return std::nullopt; + } + + // Constant-expressions are actually a very deeply nested set of rules, + // which more or less boils down to a `primary-expression` for this kind of task. + // see: https://eel.is/c++draft/expr.const.general#nt:constant-expression + // see: https://eel.is/c++draft/expr.prim.grammar#nt:primary-expression + // > constant-expression ::= primary-expression + // > primary-expression ::= literal + [[nodiscard]] + constexpr std::optional parse_constant_expression(TokenStream& stream) + { + if (auto const* const literal = peek_if(stream)) + { + stream.consume(); + return {*literal}; + } + + return std::nullopt; + } + + namespace detail + { + struct PlaceholderWrapCandidate + { + lexing::operator_or_punctuator open; + lexing::operator_or_punctuator close; + }; + + [[nodiscard]] + consteval auto make_placeholder_wrap_candidates() noexcept + { + using op = lexing::operator_or_punctuator; + std::array raw = { + PlaceholderWrapCandidate{.open = op{"{"}, .close = op{"}"}}, + PlaceholderWrapCandidate{.open = op{"<"}, .close = op{">"}}, + PlaceholderWrapCandidate{.open = op{"("}, .close = op{")"}}, + PlaceholderWrapCandidate{.open = op{"`"}, .close = op{"'"}}, + }; + + constexpr auto projection = [](auto const& e) { return e.open.index(); }; + std::ranges::sort(raw, {}, projection); + MIMICPP_ASSERT(raw.cend() == std::ranges::unique(raw, {}, projection).begin(), "Fix your input!"); + + return raw; + } + + inline constexpr std::array placeholderWrapCandidates = make_placeholder_wrap_candidates(); + + [[nodiscard]] + constexpr char const* find_end_token(TokenStream& stream, lexing::operator_or_punctuator const open, lexing::operator_or_punctuator const close) + { + while (!stream.is_eof()) + { + if (auto const* const cur = std::get_if(&stream.peek().classification)) + { + if (open == *cur) + { + stream.consume(); + std::ignore = find_end_token(stream, open, close); + continue; + } + else if (close == *cur) + { + auto const* const end = stream.peek().content.data() + stream.peek().content.size(); + stream.consume(); + + return end; + } + } + + // silently skip anything between open and close token. + stream.consume(); + } + + return nullptr; + } + } + + // This is not directly reflected in the standard, but each ecosystem has their own specific kind of representing + // e.g. anonymous types and namespaces, or lambdas. + [[nodiscard]] + constexpr std::optional parse_synthetic_id(TokenStream& stream) + { + StateGuard id{stream}; + id->isSynthetic = true; + + if (!std::holds_alternative(stream.peek().classification)) + { + return std::nullopt; + } + + auto const begin = stream.peek(); + auto const iter = util::binary_find( + detail::placeholderWrapCandidates, + std::get(begin.classification).index(), + {}, + [](auto const& e) { return e.open.index(); }); + if (iter == detail::placeholderWrapCandidates.cend()) + { + return std::nullopt; + } + + stream.consume(); + + // This recursively searches for a matching `close` token, while handling nested `open close` token-ranges. + if (auto const* const end = detail::find_end_token(stream, iter->open, iter->close)) + { + id->content = {begin.content.data(), end}; + return std::move(id).take(); + } + + return std::nullopt; + } + + // > identifier ::= lexing-id + // > identifier ::= synthetic-id + [[nodiscard]] + constexpr std::optional parse_identifier(TokenStream& stream) + { + if (auto const* const id = peek_if(stream)) + { + state::Identifier result{.content = id->content}; + stream.consume(); + return result; + } + + return parse_synthetic_id(stream); + } + + // see: https://eel.is/c++draft/temp.names#nt:template-argument + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_template_argument(TokenStream& stream) + { + if (std::optional type = parse_type_id(stream)) + { + return {state::Recursive{*std::move(type)}}; + } + /* + * template-argument: + template-argument-name + constant-expression + type-id + braced-init-list + */ + return std::nullopt; + } + + // see: https://eel.is/c++draft/temp.names#nt:template-argument-list + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_template_argument_list(TokenStream& stream) + { + std::optional first = parse_template_argument(stream); + if (!first) + { + return std::nullopt; + } + + state::TemplateArgumentList args{*std::move(first)}; + for (auto const* delimiter = peek_if(stream); + delimiter && lexing::operator_or_punctuator{","} == *delimiter; + delimiter = peek_if(stream)) + { + Transaction transaction{stream}; + stream.consume(); + + std::optional arg = parse_template_argument(stream); + if (!arg) + { + break; + } + + args.emplace_back(*std::move(arg)); + transaction.commit(); + } + + return args; + } + + // This is a custom rule, which parses enclosed template-args, but without a preceding identifier. + // > template-arg-list ::= `<` `>` + // > template-arg-list ::= `<` template-argument (`,` template-argument)* `>` + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_template_clause(TokenStream& stream) + { + StateGuard args{stream}; + + if (!expect(stream, lexing::operator_or_punctuator{"<"})) + { + return std::nullopt; + } + + if (std::optional argList = parse_template_argument_list(stream)) + { + *args = *std::move(argList); + } + + if (!expect(stream, lexing::operator_or_punctuator{">"})) + { + return std::nullopt; + } + + return std::move(args).take(); + } + + // see: https://eel.is/c++draft/dcl.decl.general#nt:cv-qualifier + [[nodiscard]] + constexpr std::optional parse_cv_qualifier(TokenStream& stream) + { + if (auto const* const keyword = peek_if(stream)) + { + constexpr auto map = make_map({ + {lexing::keyword{"const"}, state::CVQualifier::id_const }, + {lexing::keyword{"volatile"}, state::CVQualifier::id_volatile} + }); + + if (std::optional const qualifier = map(*keyword)) + { + stream.consume(); + return qualifier; + } + } + + return std::nullopt; + } + + // see: https://eel.is/c++draft/dcl.decl.general#nt:cv-qualifier-seq + // This is a simplified version of the general grammar because there are in fact just two possible qualifications. + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_cv_qualifier_seq(TokenStream& stream) + { + bool isSet{false}; + state::CVQualifierSeq qualifiers{}; + while (std::optional const qualifier = parse_cv_qualifier(stream)) + { + isSet = true; + switch (*qualifier) + { + case state::CVQualifier::id_const: + MIMICPP_ASSERT(!qualifiers.isConst, "`const` is already applied."); + qualifiers.isConst = true; + break; + + case state::CVQualifier::id_volatile: + MIMICPP_ASSERT(!qualifiers.isVolatile, "`volatile` is already applied."); + qualifiers.isVolatile = true; + break; + + default: + util::unreachable(); + } + } + + if (isSet) + { + return {qualifiers}; + } + + return std::nullopt; + } + + namespace detail + { + [[nodiscard]] + consteval auto make_simple_operator_candidates() noexcept + { + std::array texts = util::concat_arrays( + lexing::texts::comparison, + lexing::texts::assignment, + lexing::texts::incOrDec, + lexing::texts::arithmetic, + lexing::texts::bitArithmetic, + lexing::texts::logical, + std::to_array({"->", "->*", ","})); + + std::array ops = std::apply( + [](auto&... opTexts) { return std::array{lexing::operator_or_punctuator{opTexts}...}; }, + texts); + std::ranges::sort(ops, {}, &lexing::operator_or_punctuator::index); + MIMICPP_ASSERT(ops.cend() == std::ranges::unique(ops).begin(), "Fix your input!"); + + return ops; + } + + inline constexpr std::array simpleOpCandidates = make_simple_operator_candidates(); + + inline constexpr std::array doubleOpCandidates = { + std::tuple{lexing::operator_or_punctuator{"("}, lexing::operator_or_punctuator{")"}}, + std::tuple{lexing::operator_or_punctuator{"["}, lexing::operator_or_punctuator{"]"}} + }; + } + + // unqualified-id ::= `operator` op + // see:: https://eel.is/c++draft/over.oper.general#nt:operator + template + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_operator_function_id(TokenStream& stream) + { + Transaction transaction{stream}; + + if (!expect(stream, lexing::keyword{"operator"}) + && requireOperatorKeyword) + { + return std::nullopt; + } + + if (auto const* const op = peek_if(stream)) + { + if (std::ranges::binary_search(detail::simpleOpCandidates, op->index(), {}, &lexing::operator_or_punctuator::index)) + { + state::OperatorFunctionId id{.symbol = *op}; + stream.consume(); + transaction.commit(); + return id; + } + + for (auto&& [first, second] : detail::doubleOpCandidates) + { + if (expect(stream, first) + && expect(stream, second)) + { + state::OperatorFunctionId id{ + .symbol = std::array{first, second} + }; + transaction.commit(); + return id; + } + } + + return std::nullopt; + } + + if (auto const* const op = peek_if(stream)) + { + if (lexing::keyword{"new"} == *op + || lexing::keyword{"delete"} == *op) + { + std::pair symbol{*op, false}; + stream.consume(); + transaction.commit(); + + if (Transaction inner{stream}; + expect(stream, lexing::operator_or_punctuator{"["}) + && expect(stream, lexing::operator_or_punctuator{"]"})) + { + inner.commit(); + symbol.second = true; + } + + return state::OperatorFunctionId{.symbol = symbol}; + } + + if (lexing::keyword{"co_await"} == *op) + { + state::OperatorFunctionId id{ + .symbol = std::pair{*op, false} + }; + stream.consume(); + transaction.commit(); + return id; + } + + return std::nullopt; + } + + return std::nullopt; + } + + // > conversion-function-id ::= `operator` conversion-type-id + // > conversion-type-id ::= type-specifier-seq ptr-operator* + // see: https://eel.is/c++draft/class.conv.fct#nt:conversion-function-id + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_conversion_function_id(TokenStream& stream) + { + StateGuard target{stream}; + + if (!expect(stream, lexing::keyword{"operator"})) + { + return std::nullopt; + } + + std::optional base = parse_type_specifier_seq(stream); + if (!base) + { + return std::nullopt; + } + + *target = *std::move(base); + while (std::optional op = parse_ptr_operator(stream)) + { + target->declarator.root.decorations.emplace_back(*std::move(op)); + } + + return state::ConversionFunctionId{ + .target = state::Recursive{*std::move(target).take()}}; + } + + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_lambda_function_id(TokenStream& stream) + { + StateGuard identifier{stream}; + + if (!std::holds_alternative(stream.peek().classification)) + { + return std::nullopt; + } + + auto const closeOp = std::invoke([&]() -> std::optional { + if (lexing::operator_or_punctuator{"<"} == std::get(stream.peek().classification)) + { + return lexing::operator_or_punctuator{">"}; + } + + if (lexing::operator_or_punctuator{"("} == std::get(stream.peek().classification)) + { + return lexing::operator_or_punctuator{")"}; + } + + return std::nullopt; + }); + if (!closeOp) + { + return std::nullopt; + } + + auto const openToken = stream.peek(); + stream.consume(); + + if (auto const* const id = peek_if(stream); + id + && id->content.starts_with("lambda")) + { + // This recursively searches for a matching `close` token, while handling nested `open close` token-ranges. + if (auto const* const end = detail::find_end_token( + stream, + std::get(openToken.classification), + *closeOp)) + { + identifier->content = {openToken.content.data(), end}; + identifier->isMutable = expect(stream, lexing::keyword{"mutable"}).has_value(); + + return std::move(identifier).take(); + } + } + + return std::nullopt; + } + + // This rule is part of the more general unqualified-id rule. + // see: https://eel.is/c++draft/expr.prim.id.unqual#nt:unqualified-id + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_destructor_function_id(TokenStream& stream) + { + StateGuard identifier{stream}; + + if (expect(stream, lexing::operator_or_punctuator{"~"})) + { + if (std::optional id = parse_identifier(stream)) + { + identifier->name = *std::move(id); + return std::move(identifier).take(); + } + } + + return std::nullopt; + } + + // see: https://eel.is/c++draft/expr.prim.id.unqual#nt:unqualified-id + // handles + // > unqualified-id ::= identifier + // > unqualified-id ::= template-id + // > unqualified-id ::= operator-function-id + // > unqualified-id ::= conversion-function-id + // > unqualified-id ::= lambda-function-id + // > unqualified-id ::= `~`type-name + // but slightly rewritten + // + // These rules are unnecessary: + // > unqualified-id ::= literal-operator-id + // > unqualified-id ::= `~`computed-type-specifier + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_unqualified_id(TokenStream& stream) + { + StateGuard nestedId{stream}; + + // A destructor cannot be templated + if (std::optional dtor = parse_destructor_function_id(stream)) + { + nestedId->name = *std::move(dtor); + return std::move(nestedId).take(); + } + + // A synthetic lambda-id cannot be templated + if (std::optional lambda = parse_lambda_function_id(stream)) + { + nestedId->name = *std::move(lambda); + return std::move(nestedId).take(); + } + + if (std::optional op = parse_operator_function_id(stream)) + { + nestedId->name = *std::move(op); + } + // A conversion-function cannot be templated + else if (std::optional conv = parse_conversion_function_id(stream)) + { + nestedId->name = *std::move(conv); + return std::move(nestedId).take(); + } + else if (std::optional id = parse_identifier(stream)) + { + nestedId->name = *std::move(id); + } + else + { + return std::nullopt; + } + + if (std::optional templateArgs = parse_template_clause(stream)) + { + nestedId->templateArgs = *std::move(templateArgs); + } + + return std::move(nestedId).take(); + } + + // This is more or less a custom rule, which handles all nested identifier scopes; + // i.e., all parts that are terminated by a `::` token. + // > unqualified-id ::= identifier template-clause? parameters-and-qualifiers? `::` + // > unqualified-id ::= operator-function-id template-clause? parameters-and-qualifiers `::` + // > unqualified-id ::= conversion-function-id parameters-and-qualifiers `::` + // > unqualified-id ::= destructor-function-id parameters-and-qualifiers `::` + // > unqualified-id ::= lambda-id `::` + // + // Note that the additional optional `parameters-and-qualifiers` token is not reflected in the standard, + // but rather an extension due to real-life requirements. + template + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_nested_id(TokenStream& stream) + { + StateGuard nestedId{stream}; + + // A synthetic lambda-id can neither be templated nor be suffixed by an argument list + if (std::optional lambda = parse_lambda_function_id(stream)) + { + nestedId->name = *std::move(lambda); + } + else + { + bool requiresFunction{false}; + bool acceptTemplate{false}; + + // A destructor cannot be templated + if (std::optional dtor = parse_destructor_function_id(stream)) + { + requiresFunction = true; + nestedId->name = *std::move(dtor); + } + else if (std::optional opId = std::invoke([&]() -> std::optional { + Transaction innerTransaction{stream}; + if (std::optional op = parse_operator_function_id(stream)) + { + acceptTemplate = true; + innerTransaction.commit(); + return op; + } + + if constexpr (!isFirst) + { + // In general, the `operator` keyword is mandatory, but sometimes it is omitted (e.g., on msvc). + // In this case, the whole scope is just the plain operator symbol; so no template- and function-details. + if (std::optional op = parse_operator_function_id(stream); + op + // Peek here (and thus ensure that it is immediately followed by a `::` token), + // because we need to prevent false-positives. For example `::` must not be treated as `operator<`. + && peek_if(stream) + && lexing::operator_or_punctuator{"::"} == *peek_if(stream)) + { + innerTransaction.commit(); + return op; + } + } + + return std::nullopt; + })) + { + nestedId->name = *std::move(opId); + } + // A conversion-function cannot be templated + else if (std::optional conv = parse_conversion_function_id(stream)) + { + requiresFunction = true; + nestedId->name = *std::move(conv); + } + else if (std::optional id = parse_identifier(stream)) + { + acceptTemplate = true; + nestedId->name = *std::move(id); + } + else + { + return std::nullopt; + } + + if (acceptTemplate) + { + nestedId->templateArgs = parse_template_clause(stream); + } + + if (std::optional functionDecl = parse_parameters_and_qualifiers(stream)) + { + nestedId->functionDeclarator = *std::move(functionDecl); + } + else if (requiresFunction) + { + return std::nullopt; + } + } + + if (expect(stream, lexing::operator_or_punctuator{"::"})) + { + return std::move(nestedId).take(); + } + + return std::nullopt; + } + + // see: https://eel.is/c++draft/expr.prim.id.qual#nt:nested-name-specifier + // > nested-name-specifier ::= `::` + // + // These rules are not distinguishable in this task. + // > nested-name-specifier ::= type-name `::` + // > nested-name-specifier ::= namespace-name `::` + // => nested-name-specifier ::= nested-id + // + // These rules form a left-recursion: + // > nested-name-specifier ::= nested-name-specifier identifier `::` + // > nested-name-specifier ::= nested-name-specifier `template`? simple-template-id `::` + // => nested-name-specifier ::= `::` nested-id* + // => nested-name-specifier ::= nested-id+ + // + // Note that these rules are fully ignored: + // > computed-type-specifier `::` + // > splice-scope-specifier `::` + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_nested_name_specifier(TokenStream& stream) + { + StateGuard scopes{stream}; + scopes->explicitRoot = expect(stream, lexing::operator_or_punctuator{"::"}).has_value(); + + std::optional initId = parse_nested_id(stream); + if (!initId) + { + if (scopes->explicitRoot) + { + return std::move(scopes).take(); + } + + return std::nullopt; + } + + scopes->scopes.emplace_back(*std::move(initId)); + + while (!stream.is_eof()) + { + Transaction transaction{stream}; + + std::optional curId = parse_nested_id(stream); + if (!curId) + { + break; + } + + scopes->scopes.emplace_back(*std::move(curId)); + transaction.commit(); + } + + return std::move(scopes).take(); + } + + // see: https://eel.is/c++draft/expr.prim.id.qual#nt:qualified-id + // > qualified-id ::= nested-name-specifier? unqualified-id + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_qualified_id(TokenStream& stream) + { + StateGuard id{stream}; + + if (std::optional scopes = parse_nested_name_specifier(stream)) + { + id->scopes = *std::move(scopes); + } + + if (std::optional topLevelId = parse_unqualified_id(stream)) + { + id->identifier = *std::move(topLevelId); + return std::move(id).take(); + } + + return std::nullopt; + } + + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_call_convention(TokenStream& stream) + { + // Accept symbols like `__cdecl`. + if (auto const* const id = peek_if(stream); + id + && id->content.starts_with("__")) + { + state::CallConvention convention{.name = *id}; + stream.consume(); + return convention; + } + + return std::nullopt; + } + + // see: https://eel.is/c++draft/dcl.decl.general#nt:ptr-operator + // note: fully ignores `attribute-specifier-seq` + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_ptr_operator(TokenStream& stream) + { + // > ptr-operator ::= `&` + // > ptr-operator ::= `&&` + if (std::optional const ref = parse_ref_qualifier(stream)) + { + return {state::ReferenceDeclarator{.qualifier = *ref}}; + } + + // > ptr-operator ::= call-convention? nested-name-specifier? `*` cv-qualifier-seq? + // Note that `call-convention` will only be generated by msvc. + StateGuard ptr{stream}; + ptr->callConvention = parse_call_convention(stream); + ptr->scopes = parse_nested_name_specifier(stream); + if (!expect(stream, lexing::operator_or_punctuator{"*"})) + { + return std::nullopt; + } + + ptr->qualifiers = parse_cv_qualifier_seq(stream); + + return {std::move(ptr).take()}; + } + + // This rule is not directly reflected by the standard, but mirrors the reality more closely than the original + // `noptr-abstract-declarator` non-terminal, which allows that `params-and-qualifiers` non-terminals can appear between + // multiple `[ constant-expression ]` sequences. + // see: https://eel.is/c++draft/dcl.name#nt:noptr-abstract-declarator + [[nodiscard]] + constexpr std::optional parse_array_declarator(TokenStream& stream) + { + StateGuard declarator{stream}; + if (expect(stream, lexing::operator_or_punctuator{"["})) + { + declarator->size = parse_constant_expression(stream); + if (expect(stream, lexing::operator_or_punctuator{"]"})) + { + return std::move(declarator).take(); + } + } + + return std::nullopt; + } + + // see: https://eel.is/c++draft/dcl.fct#nt:parameter-declaration + // `attribute-specifier-seq`, `decl-specifier-seq` and `this` are ignored. + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_parameter_declaration(TokenStream& stream) + { + // This is just a very naive approach; does this hold? + return parse_type_id(stream); + } + + // see: https://eel.is/c++draft/dcl.fct#nt:parameter-declaration-clause + // and https://eel.is/c++draft/dcl.fct#nt:parameter-declaration-list + // The `...` is fully ignored. + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional> parse_parameter_declaration_clause(TokenStream& stream) + { + StateGuard> params{stream}; + + do + { + std::optional param = parse_parameter_declaration(stream); + if (!param) + { + // zero params are valid! + if (params->empty()) + { + break; + } + + return std::nullopt; + } + + params->emplace_back(*std::move(param)); + } + while (expect(stream, lexing::operator_or_punctuator{","})); + + return std::move(params).take(); + } + + // see: https://eel.is/c++draft/dcl.decl.general#nt:parameters-and-qualifiers + // `attribute-specifier-seq` is ignored + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_parameters_and_qualifiers(TokenStream& stream) + { + StateGuard declarator{stream}; + + if (!expect(stream, lexing::operator_or_punctuator{"("})) + { + return std::nullopt; + } + + std::optional clause = parse_parameter_declaration_clause(stream); + if (!clause) + { + return std::nullopt; + } + + if (!expect(stream, lexing::operator_or_punctuator{")"})) + { + return std::nullopt; + } + + if (std::optional cv = parse_cv_qualifier_seq(stream)) + { + declarator->qualifiers = *std::move(cv); + } + + declarator->refQualifier = parse_ref_qualifier(stream); + declarator->isNoexcept = expect(stream, lexing::keyword{"noexcept"}).has_value(); + + declarator->params.reserve(clause->size()); + std::ranges::transform( + *clause, + std::back_inserter(declarator->params), + [](state::TypeId& id) { return state::Recursive{std::move(id)}; }); + + return {std::move(declarator).take()}; + } + + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_abstract_declarator(TokenStream& stream) + { + StateGuard declarator{stream}; + auto& root = declarator->root; + + while (std::optional op = parse_ptr_operator(stream)) + { + root.decorations.emplace_back(*std::move(op)); + } + + if (Transaction nestedTransaction{stream}; + expect(stream, lexing::operator_or_punctuator{"("})) + { + if (std::optional layer = parse_abstract_declarator(stream); + layer + && expect(stream, lexing::operator_or_punctuator{")"})) + { + root.nested.emplace(std::move(layer->root)); + nestedTransaction.commit(); + } + } + + if (std::optional params = parse_parameters_and_qualifiers(stream)) + { + root.function.emplace(*std::move(params)); + } + + while (std::optional arrayDecl = parse_array_declarator(stream)) + { + root.arrays.emplace_back(*std::move(arrayDecl)); + } + + if (root.decorations.empty() + && !root.nested + && !root.function + && root.arrays.empty()) + { + return std::nullopt; + } + + return {std::move(declarator).take()}; + } + + namespace detail + { + [[nodiscard]] + consteval auto make_builtin_type_candidates() noexcept + { + std::array types = std::apply( + [](auto&... keyword) { return std::array{lexing::keyword{keyword}...}; }, + lexing::texts::typeKeywords); + std::ranges::sort(types, {}, &lexing::keyword::index); + MIMICPP_ASSERT(types.cend() == std::ranges::unique(types).begin(), "Fix your input!"); + + return types; + } + + inline constexpr std::array builtinTypeCandidates = make_builtin_type_candidates(); + } + + // see: https://eel.is/c++draft/dcl.type.general#nt:type-specifier-seq + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_type_specifier_seq(TokenStream& stream) + { + StateGuard typeId{stream}; + + std::optional qualifiedType{}; + std::optional builtinType{}; + std::optional typeKey{}; + + while (!stream.is_eof()) + { + Transaction transaction{stream}; + + if (auto const* const keyword = peek_if(stream)) + { + // just silently ignore any linkage keyword. + if (lexing::keyword{"static"} == *keyword + || lexing::keyword{"constexpr"} == *keyword) + { + stream.consume(); + transaction.commit(); + continue; + } + + if (std::optional const cv = parse_cv_qualifier(stream)) + { + if (!typeId->qualifications.apply(*cv)) + { + break; + } + + transaction.commit(); + continue; + } + + if (!qualifiedType + && std::ranges::binary_search(detail::builtinTypeCandidates, keyword->index(), {}, &lexing::keyword::index)) + { + if (!(builtinType ? *builtinType : builtinType.emplace()).try_apply(*keyword)) + { + break; + } + + stream.consume(); + transaction.commit(); + continue; + } + } + + if (!qualifiedType && !builtinType) + { + // msvc often prefixes types with e.g. `class`. + typeKey = parse_type_key(stream); + if (std::optional qualifiedId = parse_qualified_id(stream)) + { + qualifiedType = std::move(qualifiedId); + transaction.commit(); + continue; + } + } + + break; + } + + if (qualifiedType + && !builtinType) + { + qualifiedType->typeKey = std::move(typeKey); + typeId->base = *std::move(qualifiedType); + return std::move(typeId).take(); + } + + if (builtinType + && !qualifiedType + && !typeKey) + { + typeId->base = *std::move(builtinType); + return std::move(typeId).take(); + } + + return std::nullopt; + } + + // see: https://eel.is/c++draft/dcl.name#nt:type-id + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_type_id(TokenStream& stream) + { + StateGuard id{stream}; + std::optional base = parse_type_specifier_seq(stream); + if (!base) + { + return std::nullopt; + } + + *id = *std::move(base); + + if (std::optional declarator = parse_abstract_declarator(stream)) + { + id->declarator = *std::move(declarator); + } + + return std::move(id).take(); + } + + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_function(TokenStream& stream) + { + StateGuard id{stream}; + if (std::optional returnType = parse_type_id(stream)) + { + id->callConvention = parse_call_convention(stream); + + if (std::optional identifier = parse_qualified_id(stream)) + { + if (std::optional declarator = parse_parameters_and_qualifiers(stream)) + { + id->returnType = std::move(returnType); + id->identifier = *std::move(identifier); + id->identifier.identifier.functionDeclarator = *std::move(declarator); + + return std::move(id).take(); + } + } + // Well, sometimes there is no return-type given, but the parsed identifier actually is the function-id. + // E.g., `` + // This is a workaround! + else if (auto* const qualifiedId = std::get_if(&returnType->base)) + { + id->identifier = std::move(*qualifiedId); + id->identifier.identifier.functionDeclarator = std::move(returnType->declarator.root.function); + + return std::move(id).take(); + } + } + + return std::nullopt; + } +} + +namespace mimicpp::printing::type +{ + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_type(std::string_view const text) + { + lexing::NameLexer lexer{text}; + parsing::v2::TokenStream stream{lexer}; + if (std::optional typeId = parsing::v2::parse_type_id(stream); + typeId + && stream.is_eof()) + { + return typeId; + } + + return std::nullopt; + } + + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES std::optional parse_function(std::string_view const text) + { + lexing::NameLexer lexer{text}; + parsing::v2::TokenStream stream{lexer}; + if (std::optional functionId = parsing::v2::parse_function(stream)) + { + if (stream.is_eof() + // Sometimes certain template details are added in separate square-brackets + || (text.ends_with(']') + && parsing::v2::expect(stream, lexing::operator_or_punctuator{"["}))) + { + return functionId; + } + } + + return std::nullopt; + } +} + +#endif diff --git a/include/mimic++/printing/type/ParserState.hpp b/include/mimic++/printing/type/ParserState.hpp new file mode 100644 index 0000000000..078b9e7145 --- /dev/null +++ b/include/mimic++/printing/type/ParserState.hpp @@ -0,0 +1,450 @@ +// Copyright Dominic (DNKpp) Koepke 2026. +// 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_PARSER_STATE_HPP +#define MIMICPP_PRINTING_TYPE_PARSER_STATE_HPP + +#pragma once + +#include "mimic++/config/Config.hpp" +#include "mimic++/printing/type/Lexer.hpp" +#include "mimic++/utilities/C++23Backports.hpp" +#include "mimic++/utilities/CopyableBox.hpp" + +#ifndef MIMICPP_DETAIL_IS_MODULE + #include + #include + #include + #include + #include + #include +#endif + +namespace mimicpp::printing::type::parsing::v2::state +{ + struct TypeId; + + template + class Recursive + { + public: + constexpr ~Recursive(); + constexpr Recursive(Recursive const&); + constexpr Recursive& operator=(Recursive const&); + constexpr Recursive(Recursive&&) noexcept; + constexpr Recursive& operator=(Recursive&&) noexcept; + + [[nodiscard]] + explicit(false) constexpr Recursive(T value) + : m_inner{std::move(value)} + { + } + + [[nodiscard]] + constexpr T& operator*() noexcept + { + return *m_inner; + } + + [[nodiscard]] + constexpr T const& operator*() const noexcept + { + return *m_inner; + } + + [[nodiscard]] + constexpr bool operator==(Recursive const&) const; + + private: + util::CopyableBox m_inner; + }; + + template + Recursive(T) -> Recursive; + + template + Recursive(Recursive) -> Recursive; + + // see: https://eel.is/c++draft/class.pre#nt:class-key + enum ClassKey + { + id_class = 0, + id_struct, + id_union + }; + + // see: https://eel.is/c++draft/dcl.enum#nt:enum-key + enum EnumKey + { + id_enum = 0, + id_enum_class, + id_enum_struct + }; + + enum class CVQualifier + { + id_const = 0, + id_volatile + }; + + struct CVQualifierSeq + { + bool isConst{false}; + bool isVolatile{false}; + + [[nodiscard]] + bool operator==(CVQualifierSeq const&) const = default; + + [[nodiscard]] + constexpr bool apply(CVQualifier const qualifier) noexcept + { + switch (qualifier) + { + case CVQualifier::id_const: + return !std::exchange(isConst, true); + + case CVQualifier::id_volatile: + return !std::exchange(isVolatile, true); + + default: + util::unreachable(); + } + } + }; + + enum RefQualifier + { + id_ref = 0, + id_refref + }; + + using ConstantExpression = lexing::literal; + + struct Identifier + { + std::string_view content{}; + bool isSynthetic{false}; + + [[nodiscard]] + bool operator==(Identifier const&) const = default; + }; + + struct FunctionDeclarator + { + std::vector> params{}; + CVQualifierSeq qualifiers{}; + std::optional refQualifier{}; + bool isNoexcept{false}; + + [[nodiscard]] + bool operator==(FunctionDeclarator const&) const = default; + }; + + using TemplateArgument = std::variant< + ConstantExpression, + Recursive>; + + using TemplateArgumentList = std::vector; + + struct OperatorFunctionId + { + using Symbol = std::variant< + lexing::operator_or_punctuator, + std::pair, + std::array>; + Symbol symbol; + + [[nodiscard]] + constexpr bool is_call() const noexcept + { + if (auto const* const doubleOp = std::get_if>(&symbol)) + { + return lexing::operator_or_punctuator{"("} == doubleOp->front() + && lexing::operator_or_punctuator{")"} == doubleOp->back(); + } + + return false; + } + + [[nodiscard]] + bool operator==(OperatorFunctionId const&) const = default; + }; + + struct ConversionFunctionId + { + Recursive target; + + [[nodiscard]] + bool operator==(ConversionFunctionId const&) const = default; + }; + + struct LambdaFunctionId + { + std::string_view content; + bool isMutable{false}; + + [[nodiscard]] + bool operator==(LambdaFunctionId const&) const = default; + }; + + struct DestructorFunctionId + { + Identifier name; + + [[nodiscard]] + bool operator==(DestructorFunctionId const&) const = default; + }; + + struct UnqualifiedId + { + using Name = std::variant< + Identifier, + OperatorFunctionId, + ConversionFunctionId, + LambdaFunctionId, + DestructorFunctionId>; + Name name{}; + std::optional templateArgs{}; + std::optional functionDeclarator{}; + + [[nodiscard]] + bool operator==(UnqualifiedId const&) const = default; + }; + + // This models more or less: https://eel.is/c++draft/expr.prim.id.qual#nt:nested-name-specifier + struct ScopeSequence + { + bool explicitRoot{}; + std::vector scopes{}; + + [[nodiscard]] + bool operator==(ScopeSequence const&) const = default; + }; + + using TypeKey = std::variant; + + // see: https://eel.is/c++draft/expr.prim.id.qual#nt:qualified-id + struct QualifiedId + { + ScopeSequence scopes{}; + UnqualifiedId identifier{}; + std::optional typeKey{}; + + [[nodiscard]] + bool operator==(QualifiedId const&) const = default; + }; + + struct ArrayDeclarator + { + std::optional size{}; + + [[nodiscard]] + bool operator==(ArrayDeclarator const&) const = default; + }; + + struct CallConvention + { + lexing::identifier name{}; + + [[nodiscard]] + bool operator==(CallConvention const&) const = default; + }; + + struct PointerDeclarator + { + std::optional qualifiers{}; + std::optional scopes{}; + std::optional callConvention{}; + + [[nodiscard]] + bool operator==(PointerDeclarator const&) const = default; + }; + + struct ReferenceDeclarator + { + RefQualifier qualifier{}; + + [[nodiscard]] + bool operator==(ReferenceDeclarator const&) const = default; + }; + + using PtrOperator = std::variant< + ReferenceDeclarator, + PointerDeclarator>; + + struct AbstractDeclarator + { + struct Layer + { + std::vector decorations{}; + std::optional> nested{}; + std::optional function{}; + std::vector arrays{}; + + [[nodiscard]] + bool operator==(Layer const&) const = default; + }; + + Layer root{}; + + [[nodiscard]] + bool operator==(AbstractDeclarator const&) const = default; + }; + + struct BuiltinType + { + std::optional base{}; + + enum class SizeSpec : std::int8_t + { + id_short = 0, + id_long, + id_longlong + }; + + std::optional sizeSpec{}; + + enum class SignedSpec : std::int8_t + { + id_signed = 0, + id_unsigned + }; + + std::optional signedSpec{}; + + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES bool try_apply(lexing::keyword const& keyword) + { + return try_apply_size_spec(keyword) + || try_apply_signed_spec(keyword) + || try_apply_base(keyword); + } + + [[nodiscard]] + bool operator==(BuiltinType const&) const = default; + + private: + [[nodiscard]] + constexpr bool try_apply_base(lexing::keyword const& keyword) noexcept + { + // Todo: verify correct type keywords + if (!base) + { + base = keyword; + return true; + } + + return false; + } + + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES bool try_apply_signed_spec(lexing::keyword const& keyword) noexcept + { + std::optional const spec = std::invoke([&]() -> std::optional { + if (lexing::keyword{"unsigned"} == keyword) + { + return SignedSpec::id_unsigned; + } + + if (lexing::keyword{"signed"} == keyword) + { + return SignedSpec::id_signed; + } + + return std::nullopt; + }); + + if (spec + && !signedSpec) + { + signedSpec = spec; + return true; + } + + return false; + } + + [[nodiscard]] + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES bool try_apply_size_spec(lexing::keyword const& keyword) noexcept + { + std::optional const spec = std::invoke([&]() -> std::optional { + if (lexing::keyword{"long"} == keyword) + { + return SizeSpec::id_long; + } + + if (lexing::keyword{"short"} == keyword) + { + return SizeSpec::id_short; + } + + // This is a msvc symbol + if (lexing::keyword{"__int64"} == keyword) + { + return SizeSpec::id_longlong; + } + + return std::nullopt; + }); + + if (spec) + { + if (!sizeSpec) + { + sizeSpec = spec; + return true; + } + + if (*sizeSpec == SizeSpec::id_long + && *spec == SizeSpec::id_long) + { + sizeSpec = SizeSpec::id_longlong; + return true; + } + } + + return false; + } + }; + + using BaseType = std::variant< + QualifiedId, + BuiltinType>; + + struct TypeId + { + CVQualifierSeq qualifications{}; + BaseType base; + AbstractDeclarator declarator{}; + + [[nodiscard]] + bool operator==(TypeId const&) const = default; + }; + + struct FunctionId + { + std::optional returnType{}; + std::optional callConvention{}; + QualifiedId identifier{}; + + [[nodiscard]] + bool operator==(FunctionId const&) const = default; + }; + + template + constexpr Recursive::~Recursive() = default; + template + constexpr Recursive::Recursive(Recursive const&) = default; + template + constexpr Recursive& Recursive::operator=(Recursive const&) = default; + template + constexpr Recursive::Recursive(Recursive&&) noexcept = default; + template + constexpr Recursive& Recursive::operator=(Recursive&&) noexcept = default; + template + constexpr bool Recursive::operator==(Recursive const&) const = default; +} + +#endif diff --git a/include/mimic++/printing/type/PrintType.hpp b/include/mimic++/printing/type/PrintType.hpp index 91a39a23d6..0bdc911802 100644 --- a/include/mimic++/printing/type/PrintType.hpp +++ b/include/mimic++/printing/type/PrintType.hpp @@ -1,4 +1,4 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 2025. +// Copyright Dominic (DNKpp) Koepke 2024-2026. // 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) @@ -13,6 +13,7 @@ #include "mimic++/printing/Format.hpp" #include "mimic++/printing/Fwd.hpp" #include "mimic++/utilities/PriorityTag.hpp" +#include "mimic++/utilities/SourceLocation.hpp" #ifndef MIMICPP_DETAIL_IS_MODULE #include @@ -20,32 +21,66 @@ #include #include #include - #include #include #endif MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp::printing::type { + namespace detail + { + template + [[nodiscard]] + consteval std::string_view raw_type_name() noexcept + { + return util::SourceLocation{}.function_name(); + } + + // GCOVR_EXCL_START + inline constexpr auto typeNameConfig = std::invoke([] { + auto const rawName = raw_type_name(); + std::string_view const intName{"int"}; + std::size_t const prefix = rawName.rfind(intName); + MIMICPP_ASSERT(prefix != std::string_view::npos, "Did not find `int` in the type-name string."); + + struct type_name_config + { + std::size_t prefix{}; + std::size_t suffix{}; + }; + + return type_name_config{ + .prefix = prefix, + .suffix = rawName.size() - intName.size() - prefix}; + }); + // GCOVR_EXCL_STOP + } + /** - * \brief Returns the (potentially demangled) name. + * \brief Returns the raw type-name. * \ingroup PRINTING_TYPE * \tparam T The desired type. - * \return The (potentially demangled) name. - * \details This function serves as a wrapper for typeid(T).name(). - * - On MSVC, this function returns the demangled name directly. - * - However, on GCC and Clang, the behavior differs. - * When `MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES` is enabled, it further demangles the name using `abi::__cxa_demangle`. + * \return The raw name. + * \details + * This function internally relies on `std::source_location::function_name` + * and tries to extract the desired type-name from that function-name. */ template [[nodiscard]] - StringT type_name(); + consteval std::string_view type_name() noexcept + { + auto typeName = detail::raw_type_name(); + typeName.remove_prefix(detail::typeNameConfig.prefix); + typeName.remove_suffix(detail::typeNameConfig.suffix); + + return typeName; + } /** - * \brief Prettifies a demangled name. + * \brief Prettifies a typen-name. * \ingroup PRINTING_TYPE * \tparam OutIter The print-iterator type. * \param out The print iterator. - * \param name The demangled name to be prettified. + * \param name The name to be prettified. * \return The current print iterator. * * \details This function formats a type or template name for better readability. @@ -60,10 +95,10 @@ MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp::printing::type * this function simply outputs the provided name without any modifications. */ template - MIMICPP_DETAIL_CONSTEXPR_STRING OutIter prettify_type(OutIter out, StringT name); + constexpr OutIter prettify_type(OutIter out, std::string_view name); /** - * \brief Prettifies a function name produces by e.g. `std::source_location::function_name()`. + * \brief Prettifies a function name, generated by e.g. `std::source_location::function_name()`. * \ingroup PRINTING_TYPE * \tparam OutIter The print-iterator type. * \param out The print iterator. @@ -81,115 +116,42 @@ MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp::printing::type * this function simply outputs the provided name without any modifications. */ template - MIMICPP_DETAIL_CONSTEXPR_STRING OutIter prettify_function(OutIter out, StringT name); + constexpr OutIter prettify_function(OutIter out, std::string_view name); } #ifdef MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES - #if MIMICPP_DETAIL_IS_GCC || MIMICPP_DETAIL_IS_CLANG - - #ifndef MIMICPP_DETAIL_IS_MODULE - #include - #include - #include - #endif + #include "mimic++/printing/type/Parser.hpp" + #include "mimic++/printing/type/PrintVisitor.hpp" namespace mimicpp::printing::type { - namespace detail + template + constexpr OutIter prettify_type(OutIter out, std::string_view const name) { - struct free_deleter + if (std::optional const typeId = parse_type(name)) { - void operator()(char* const c) const noexcept - { - std::free(c); - } - }; - } - - template - StringT type_name() - { - auto* const rawName = typeid(T).name(); + parsing::v2::PrintVisitor visitor{std::move(out)}; + visitor.visit(*typeId); - // see: https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_demangling.html - int status{}; - std::unique_ptr const demangledName{ - abi::__cxa_demangle(rawName, nullptr, nullptr, &status)}; - if (0 == status) - { - return {demangledName.get()}; + return visitor.out(); } - return {rawName}; - } -} - - #else - -namespace mimicpp::printing::type -{ - template - StringT type_name() - { - return typeid(T).name(); + return format::format_to(std::move(out), "{}", name); } -} - #endif - - #include "mimic++/printing/type/NameParser.hpp" - #include "mimic++/printing/type/NamePrintVisitor.hpp" - -namespace mimicpp::printing::type -{ template - MIMICPP_DETAIL_CONSTEXPR_STRING 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 + constexpr OutIter prettify_function(OutIter out, std::string_view const name) { - [[nodiscard]] - MIMICPP_DETAIL_CONSTEXPR_STRING StringT remove_template_details(StringT name) + if (std::optional const functionId = parse_function(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 - MIMICPP_DETAIL_CONSTEXPR_STRING OutIter prettify_function(OutIter out, StringT name) - { - name = detail::remove_template_details(std::move(name)); + parsing::v2::PrintVisitor visitor{std::move(out)}; + visitor.visit(*functionId); - static_assert(parsing::parser_visitor>); - - PrintVisitor visitor{std::move(out)}; - parsing::NameParser parser{std::ref(visitor), name}; - parser.parse_function(); + return visitor.out(); + } - return visitor.out(); + return format::format_to(std::move(out), "{}", name); } } @@ -197,20 +159,14 @@ namespace mimicpp::printing::type namespace mimicpp::printing::type { - template - StringT type_name() - { - return typeid(T).name(); - } - template - MIMICPP_DETAIL_CONSTEXPR_STRING OutIter prettify_type(OutIter out, StringT name) + constexpr OutIter prettify_type(OutIter out, std::string_view const name) { return std::ranges::copy(name, std::move(out)).out; } template - MIMICPP_DETAIL_CONSTEXPR_STRING OutIter prettify_function(OutIter out, StringT name) + constexpr OutIter prettify_function(OutIter out, std::string_view const name) { return std::ranges::copy(name, std::move(out)).out; } @@ -424,24 +380,23 @@ MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp /** * \defgroup PRINTING_TYPE object-type stringification * \ingroup PRINTING - * \brief State stringification occurs when an object's type is transformed into a textual representation. + * \brief Type stringification occurs when an object's type is transformed into a textual representation. * \details Type stringification requires special care in C++, * as there is no portable way to print types exactly as users would write them in code. - * To address this, ``mimic++`` introduces the ``print_type`` function, + * To address this, *mimic++* introduces the `print_type` function, * which internally removes much of the extraneous noise that arises from various standard implementations. * - * Furthermore, ``mimic++`` aims to present the majority of types in a manner that closely resembles how users would write them. + * Furthermore, *mimic++* aims to present the majority of types in a manner that closely resembles how users would write them. * For class types, this is achievable with some additional text manipulation; * however, there is no way to determine whether users used the actual class name or an alias. - * For example, ``std::string`` is actually the type ``std::basic_string, std::allocator>``, + * For example, `std::string` is actually the type `std::basic_string, std::allocator>`, * which introduces a significant amount of noise. * - * Since it is common for users to write ``std::string`` rather than using the ``std::basic_string`` template form, - * ``mimic++`` specifically handles these common types and will always print ``std::string`` when the exact long form is detected. + * Since it is common for users to write `std::string` rather than using the `std::basic_string` template form, + * *mimic++* specifically handles these common types and will always print `std::string` when the exact long form is detected. * - * However, there are dozens of template types in the STL and even more in user code; and mimic++ strives to handle them correctly. - * When the name of a template type is requested, ``mimic++`` examines each of its arguments recursively and - * even folds all default arguments. + * However, there are dozens of template types in the STL and even more in user code; and *mimic++* strives to handle them correctly. + * When the name of a template type is requested, *mimic++* examines each of its arguments recursively and even folds all default arguments. * * For example, the type * ```cpp @@ -451,21 +406,19 @@ MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp * std::less, * std::allocator>>>> * ``` - * will be printed as ``std::map>`` which is likely how users have written it. + * will be printed as `std::map>` which is likely how users have written it. * * ### Customize type-stringification * - * ``mimic++`` automatically converts every type into a meaningful textual representation, - * but users may sometimes want to customize this process. - * Users can create a specialization of ``mimicpp::custom::TypePrinter`` for their any type, which will then be - * prioritized over the internal conversions. + * *mimic++* automatically converts every type into a meaningful textual representation, but users may sometimes want to customize this process. + * Users can create a specialization of `mimicpp::custom::TypePrinter` for their type, which will then be prioritized over the internal conversions. * * Consider the following type. * \snippet CustomPrinter.cpp my_type * Users can then create a specialization as follows: * \snippet CustomPrinter.cpp my_type type-printer * - * When the name of the (potentially cv-ref qualified) ``my_type`` is requested via ``print_type``, that specification will be used: + * When the name of the (potentially cv-ref qualified) `my_type` is requested via `print_type`, that specification will be used: * \snippet CustomPrinter.cpp my_type type-print * *\{ diff --git a/include/mimic++/printing/type/PrintVisitor.hpp b/include/mimic++/printing/type/PrintVisitor.hpp new file mode 100644 index 0000000000..03905f9fe4 --- /dev/null +++ b/include/mimic++/printing/type/PrintVisitor.hpp @@ -0,0 +1,515 @@ +// Copyright Dominic (DNKpp) Koepke 2026. +// 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_PRINT_VISITOR_HPP +#define MIMICPP_PRINTING_TYPE_PRINT_VISITOR_HPP + +#pragma once + +#include "mimic++/config/Config.hpp" +#include "mimic++/printing/Format.hpp" +#include "mimic++/printing/type/ParserState.hpp" +#include "mimic++/utilities/C++23Backports.hpp" +#include "mimic++/utilities/Overloaded.hpp" + +#ifndef MIMICPP_DETAIL_IS_MODULE + #include + #include +#endif + +namespace mimicpp::printing::type::parsing::v2 +{ + template + class PrintVisitor + { + public: + [[nodiscard]] + explicit constexpr PrintVisitor(OutIter out) + : m_OutIter{std::move(out)} + { + } + + [[nodiscard]] + constexpr OutIter out() const + { + return m_OutIter; + } + + constexpr void visit(state::TypeId const& typeId) + { + std::visit( + [&](auto& base) { visit(base); }, + typeId.base); + visit(typeId.qualifications); + visit(typeId.declarator.root); + } + + constexpr void visit(state::FunctionId const& functionId) + { + if (functionId.returnType) + { + visit(*functionId.returnType); + m_OutIter = format::format_to(std::move(m_OutIter), " "); + } + + visit(functionId.identifier); + } + + private: + OutIter m_OutIter; + int m_NestedDepth{0}; + + template + constexpr void visit(state::Recursive const& state) + { + visit(*state); + } + + constexpr void visit(state::BuiltinType::SignedSpec const spec) + { + switch (spec) + { + using Spec = state::BuiltinType::SignedSpec; + case Spec::id_signed: + m_OutIter = format::format_to(std::move(m_OutIter), "signed"); + break; + case Spec::id_unsigned: + m_OutIter = format::format_to(std::move(m_OutIter), "unsigned"); + break; + + // GCOVR_EXCL_START + default: + util::unreachable(); + // GCOVR_EXCL_STOP + } + } + + constexpr void visit(state::BuiltinType::SizeSpec const spec) + { + switch (spec) + { + using Spec = state::BuiltinType::SizeSpec; + case Spec::id_short: + m_OutIter = format::format_to(std::move(m_OutIter), "short"); + break; + case Spec::id_long: + m_OutIter = format::format_to(std::move(m_OutIter), "long"); + break; + case Spec::id_longlong: + m_OutIter = format::format_to(std::move(m_OutIter), "long long"); + break; + + // GCOVR_EXCL_START + default: + util::unreachable(); + // GCOVR_EXCL_STOP + } + } + + constexpr void visit(state::BuiltinType const& type) + { + bool first = true; + + if (type.signedSpec) + { + visit(*type.signedSpec); + first = false; + } + + if (type.sizeSpec) + { + if (!std::exchange(first, false)) + { + m_OutIter = format::format_to(std::move(m_OutIter), " "); + } + + visit(*type.sizeSpec); + } + + if (type.base + // Do not print redundant `int` when a size-spec is applied. + && (!type.sizeSpec || lexing::keyword{"int"} != *type.base)) + { + if (!std::exchange(first, false)) + { + m_OutIter = format::format_to(std::move(m_OutIter), " "); + } + + m_OutIter = format::format_to(std::move(m_OutIter), "{}", type.base->text()); + } + } + + constexpr void visit(state::RefQualifier const qualifier) + { + switch (qualifier) + { + using Qualifier = state::RefQualifier; + case Qualifier::id_ref: + m_OutIter = format::format_to(std::move(m_OutIter), "&"); + break; + case Qualifier::id_refref: + m_OutIter = format::format_to(std::move(m_OutIter), "&&"); + break; + + // GCOVR_EXCL_START + default: + util::unreachable(); + // GCOVR_EXCL_STOP + } + } + + constexpr void visit(state::CVQualifierSeq const qualifiers) + { + if (qualifiers.isConst) + { + m_OutIter = format::format_to(m_OutIter, " const"); + } + + if (qualifiers.isVolatile) + { + m_OutIter = format::format_to(m_OutIter, " volatile"); + } + } + + constexpr void visit(state::ConstantExpression const& expression) + { + m_OutIter = format::format_to(m_OutIter, "{}", expression.content); + } + + static constexpr std::array syntheticAliases = std::to_array>({ + {"{anonymous}", "{anon-ns}" }, + {"(anonymous namespace)", "{anon-ns}" }, + + {"`anonymous-namespace'", "{anon-ns}" }, // msvc + {"`anonymous namespace'", "{anon-ns}" }, // msvc + {"", ""}, // msvc + }); + + constexpr void visit(state::Identifier const& id) + { + if (id.isSynthetic) + { + if (auto const iter = std::ranges::find(syntheticAliases, id.content, [](auto const& e) { return e.first; }); + iter != syntheticAliases.cend()) + { + m_OutIter = format::format_to(std::move(m_OutIter), "{}", iter->second); + return; + } + + // These synthetic ids will be generated on clang: + if (constexpr std::string_view prefix{"(unnamed "}, suffix{")"}; + id.content.starts_with(prefix) + && id.content.ends_with(suffix)) + { + std::string_view classType = id.content; + classType.remove_prefix(prefix.size()); + classType.remove_suffix(suffix.size()); + classType = classType.substr(0, classType.find(" at ")); + + m_OutIter = format::format_to(std::move(m_OutIter), "", classType); + return; + } + + // These synthetic ids will be generated on clang as well. + if (constexpr std::string_view prefix{"(anonymous "}, suffix{")"}; + id.content.starts_with(prefix) + && id.content.ends_with(suffix)) + { + std::string_view classType = id.content; + classType.remove_prefix(prefix.size()); + classType.remove_suffix(suffix.size()); + classType = classType.substr(0, classType.find(" at ")); + + m_OutIter = format::format_to(std::move(m_OutIter), "", classType); + return; + } + + // These synthetic ids will be generated on msvc. + if (constexpr std::string_view prefix{""}; + id.content.starts_with(prefix) + && id.content.ends_with(suffix)) + { + std::string_view classType = id.content; + classType.remove_prefix(prefix.size()); + classType.remove_suffix(suffix.size()); + + m_OutIter = format::format_to(std::move(m_OutIter), "", classType); + return; + } + } + + m_OutIter = format::format_to(std::move(m_OutIter), "{}", id.content); + } + + constexpr void visit(state::PointerDeclarator const& declarator) + { + if (declarator.scopes) + { + visit(*declarator.scopes); + } + + m_OutIter = format::format_to(std::move(m_OutIter), "*"); + + if (declarator.qualifiers) + { + visit(*declarator.qualifiers); + } + } + + constexpr void visit(state::ReferenceDeclarator const& declarator) + { + visit(declarator.qualifier); + } + + constexpr void visit(state::ArrayDeclarator const& declarator) + { + m_OutIter = format::format_to(std::move(m_OutIter), "["); + + if (declarator.size) + { + visit(*declarator.size); + } + + m_OutIter = format::format_to(std::move(m_OutIter), "]"); + } + + MIMICPP_DETAIL_CONSTEXPR_PRETTY_TYPES void visit(state::FunctionDeclarator const& declarator) + { + if (0 < m_NestedDepth) + { + return; + } + + m_OutIter = format::format_to(std::move(m_OutIter), "("); + + // Sometimes params just contain a single (`ret (void)`), which will be suppressed. + state::TypeId const voidTypeId{.base = state::BuiltinType{.base = lexing::keyword{"void"}}}; + if (1u != declarator.params.size() + || voidTypeId != declarator.params.front()) + { + ++m_NestedDepth; + join( + declarator.params, + ", ", + "", + [&](auto const& arg) { visit(arg); }); + --m_NestedDepth; + } + + m_OutIter = format::format_to(std::move(m_OutIter), ")"); + + visit(declarator.qualifiers); + if (declarator.refQualifier) + { + visit(*declarator.refQualifier); + } + + if (declarator.isNoexcept) + { + m_OutIter = format::format_to(std::move(m_OutIter), " noexcept"); + } + } + + constexpr void visit(state::TemplateArgumentList const& args) + { + if (0 < m_NestedDepth) + { + return; + } + + m_OutIter = format::format_to(std::move(m_OutIter), "<"); + + ++m_NestedDepth; + join( + args, + ", ", + "", + [this](auto const& arg) { + std::visit( + [&](auto const& inner) { visit(inner); }, + arg); + }); + --m_NestedDepth; + + m_OutIter = format::format_to(std::move(m_OutIter), ">"); + } + + constexpr void visit(state::OperatorFunctionId const& id) + { + m_OutIter = format::format_to(std::move(m_OutIter), "operator"); + std::visit( + util::Overloaded{ + [this](lexing::operator_or_punctuator const& op) { + m_OutIter = format::format_to(std::move(m_OutIter), "{}", op.text()); + }, + [this](std::pair const& op) { + m_OutIter = format::format_to(std::move(m_OutIter), " {}", op.first.text()); + if (op.second) + { + m_OutIter = format::format_to(std::move(m_OutIter), "[]"); + } + }, + [this](std::span const op) { + m_OutIter = format::format_to( + std::move(m_OutIter), + "{}{}", + op.front().text(), + op.back().text()); + }}, + id.symbol); + } + + constexpr void visit(state::ConversionFunctionId const& id) + { + m_OutIter = format::format_to(std::move(m_OutIter), "operator "); + visit(id.target); + } + + constexpr void visit(state::DestructorFunctionId const& id) + { + m_OutIter = format::format_to(std::move(m_OutIter), "~{}", id.name.content); + } + + constexpr void visit(state::LambdaFunctionId const& id) + { + m_OutIter = format::format_to(std::move(m_OutIter), ""); + + if (id.isMutable) + { + m_OutIter = format::format_to(std::move(m_OutIter), " mutable"); + } + } + + constexpr void visit(state::UnqualifiedId const& nested) + { + std::visit( + [&](auto const& inner) { visit(inner); }, + nested.name); + + if (nested.templateArgs) + { + visit(*nested.templateArgs); + } + + if (nested.functionDeclarator) + { + visit(*nested.functionDeclarator); + } + } + + constexpr void visit(std::span scopes) + { + while (!scopes.empty()) + { + auto const& current = scopes.front(); + scopes = scopes.subspan(1u); + + if (auto const* const identifier = std::get_if(¤t.name); + identifier + && "__cxx11" == identifier->content) + { + continue; + } + + visit(current); + m_OutIter = format::format_to(std::move(m_OutIter), "::"); + + // msvc appends an additional `()::` scope to lambdas, which the parser correctly identifies as `operator()::`. + // However, this is unnecessary noise, which I'd like to omit. + if (std::get_if(¤t.name) + && !scopes.empty()) + { + if (auto const* const next = std::get_if(&scopes.front().name); + next + && next->is_call()) + { + scopes = scopes.subspan(1u); + } + } + // msvc does not emit `~id` for destructors, but a synthetic `{dtor}` identifier instead. + // So, let's rebuild the actual destructor identifier with the help of the previous identifier.. + else if (auto const* const currentId = std::get_if(¤t.name); + currentId + && !scopes.empty()) + { + if (auto const* const next = std::get_if(&scopes.front().name); + next + && "{dtor}" == next->content) + { + visit(state::DestructorFunctionId{.name = *currentId}); + m_OutIter = format::format_to(std::move(m_OutIter), "::"); + scopes = scopes.subspan(1u); + } + } + } + } + + constexpr void visit(state::ScopeSequence const& sequence) + { + if (sequence.explicitRoot) + { + m_OutIter = format::format_to(std::move(m_OutIter), "::"); + } + + ++m_NestedDepth; + visit(sequence.scopes); + --m_NestedDepth; + } + + constexpr void visit(state::QualifiedId const& id) + { + visit(id.scopes); + visit(id.identifier); + } + + constexpr void visit(state::AbstractDeclarator::Layer const& declarator) + { + for (auto const& decoration : declarator.decorations) + { + std::visit( + [&](auto const& inner) { visit(inner); }, + decoration); + } + + if (declarator.nested) + { + m_OutIter = format::format_to(std::move(m_OutIter), "("); + visit(*declarator.nested); + m_OutIter = format::format_to(std::move(m_OutIter), ")"); + } + + if (declarator.function) + { + visit(*declarator.function); + } + + std::ranges::for_each( + declarator.arrays, + [&](auto const& arr) { visit(arr); }); + } + + template + constexpr void join( + Range&& range, + std::string_view const separator, + std::string_view const terminator, + std::invocable> auto action) + { + if (std::ranges::subrange elements{range}) + { + std::invoke(action, elements.front()); + for (auto const& element : elements.advance(1u)) + { + m_OutIter = format::format_to(std::move(m_OutIter), "{}", separator); + std::invoke(action, element); + } + + m_OutIter = format::format_to(std::move(m_OutIter), "{}", terminator); + } + } + }; +} + +#endif diff --git a/include/mimic++/printing/type/Templated.hpp b/include/mimic++/printing/type/Templated.hpp index f8d7fb4e10..a35e39440f 100644 --- a/include/mimic++/printing/type/Templated.hpp +++ b/include/mimic++/printing/type/Templated.hpp @@ -1,4 +1,4 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 2025. +// Copyright Dominic (DNKpp) Koepke 2024-2026. // 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) @@ -32,10 +32,10 @@ namespace mimicpp::printing::type::detail template constexpr OutIter pretty_template_name(OutIter out) { - StringT name = type_name(); - auto const iter = std::ranges::find(name, '<'); - MIMICPP_ASSERT(iter != name.cend(), "Given name is not a template."); - name.erase(iter, name.end()); + std::string_view name = type_name(); + auto const templateArgListPos = name.find('<'); + MIMICPP_ASSERT(templateArgListPos != std::string_view::npos, "Given name is not a template."); + name = name.substr(0u, templateArgListPos); return type::prettify_type(std::move(out), std::move(name)); } diff --git a/include/mimic++/utilities/CopyableBox.hpp b/include/mimic++/utilities/CopyableBox.hpp new file mode 100644 index 0000000000..bff5c4f705 --- /dev/null +++ b/include/mimic++/utilities/CopyableBox.hpp @@ -0,0 +1,117 @@ +// Copyright Dominic (DNKpp) Koepke 2026. +// 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_COPYABLE_BOX_HPP +#define MIMICPP_UTILITIES_COPYABLE_BOX_HPP + +#pragma once + +#include "mimic++/Fwd.hpp" +#include "mimic++/config/Config.hpp" + +#ifndef MIMICPP_DETAIL_IS_MODULE + #include + #include + #include +#endif + +MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp::util +{ + /** + * \brief Simple copyable value type, which owns a heap-allocated value. + * \ingroup UTILITIES + * \details + * Unlike the common `std::unique_ptr`, this type is directly copyable. + * Besides that, this type is fully `constexpr` (unlike `std::unique_ptr` before C++23). + */ + template + class CopyableBox + { + public: + constexpr ~CopyableBox() noexcept + { + delete m_Data; + } + + [[nodiscard]] + constexpr CopyableBox(CopyableBox const& other) + : m_Data{other.m_Data ? new T{*other} : nullptr} + { + } + + [[nodiscard]] + constexpr CopyableBox(CopyableBox&& other) noexcept + : m_Data{std::exchange(other.m_Data, nullptr)} + { + } + + constexpr CopyableBox& operator=(CopyableBox other) noexcept + { + std::ranges::swap(m_Data, other.m_Data); + return *this; + } + + [[nodiscard]] + constexpr CopyableBox() + : m_Data{new T{}} + { + } + + [[nodiscard]] + explicit(false) constexpr CopyableBox(T const& value) + : m_Data{new T{value}} + { + } + + [[nodiscard]] + explicit(false) constexpr CopyableBox(T&& value) + : m_Data{new T{std::move(value)}} + { + } + + [[nodiscard]] + constexpr T& operator*() noexcept + { + MIMICPP_ASSERT(m_Data, "Data is null."); + return *m_Data; + } + + [[nodiscard]] + constexpr T const& operator*() const noexcept + { + MIMICPP_ASSERT(m_Data, "Data is null."); + return *m_Data; + } + + [[nodiscard]] + constexpr T* operator->() noexcept + { + return m_Data; + } + + [[nodiscard]] + constexpr T const* operator->() const noexcept + { + return m_Data; + } + + [[nodiscard]] + explicit constexpr operator bool() const noexcept + { + return m_Data != nullptr; + } + + [[nodiscard]] + friend constexpr bool operator==(CopyableBox const& lhs, CopyableBox const& rhs) + { + return *lhs == *rhs; + } + + private: + T* m_Data{}; + }; +} + +#endif diff --git a/include/mimic++/utilities/Overloaded.hpp b/include/mimic++/utilities/Overloaded.hpp new file mode 100644 index 0000000000..0a687849db --- /dev/null +++ b/include/mimic++/utilities/Overloaded.hpp @@ -0,0 +1,29 @@ +// Copyright Dominic (DNKpp) Koepke 2026. +// 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_OVERLOADED_HPP +#define MIMICPP_UTILITIES_OVERLOADED_HPP + +#pragma once + +namespace mimicpp::util +{ + /** + * \brief Famous overloaded helper type used for convenient variant visiting. + * \ingroup UTILITIES + * \see https://www.cppstories.com/2019/02/2lines3featuresoverload.html/ + */ + template + struct Overloaded + : public Ts... + { + using Ts::operator()...; + }; + + template + Overloaded(Ts...) -> Overloaded; +} + +#endif diff --git a/include/mimic++/utilities/SourceLocation.hpp b/include/mimic++/utilities/SourceLocation.hpp index fe1cdecfdd..dd06be2f7d 100644 --- a/include/mimic++/utilities/SourceLocation.hpp +++ b/include/mimic++/utilities/SourceLocation.hpp @@ -1,4 +1,4 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 2025. +// Copyright Dominic (DNKpp) Koepke 2024-2026. // 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) @@ -10,18 +10,13 @@ #include "mimic++/config/Config.hpp" #include "mimic++/printing/Fwd.hpp" -#include "mimic++/printing/PathPrinter.hpp" -#include "mimic++/printing/state/CommonTypes.hpp" -#include "mimic++/printing/type/PrintType.hpp" #ifndef MIMICPP_DETAIL_IS_MODULE #include #include - #include - #ifdef __cpp_lib_source_location + #ifdef MIMICPP_DETAIL_HAS_SOURCE_LOCATION #include - #define MIMICPP_DETAIL_HAS_SOURCE_LOCATION 1 #endif #endif @@ -115,18 +110,4 @@ MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp::util }; } -template <> -struct mimicpp::printing::detail::state::common_type_printer -{ - template - static constexpr OutIter print(OutIter out, util::SourceLocation const& loc) - { - return detail::print_source_location( - std::move(out), - loc.file_name(), - loc.line(), - loc.function_name()); - } -}; - #endif diff --git a/include/mimic++/utilities/StaticString.hpp b/include/mimic++/utilities/StaticString.hpp index 62a8de9344..deee50ff53 100644 --- a/include/mimic++/utilities/StaticString.hpp +++ b/include/mimic++/utilities/StaticString.hpp @@ -1,4 +1,4 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 2025. +// Copyright Dominic (DNKpp) Koepke 2024-2026. // 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) @@ -11,8 +11,11 @@ #ifndef MIMICPP_DETAIL_IS_MODULE #include + #include + #include #include - #include + #include + #include #endif MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp::util @@ -21,18 +24,19 @@ MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp::util class StaticString { public: - Char data[length]; + // We intentionally keep the null-terminator. + std::array data; [[nodiscard]] // - explicit(false) consteval StaticString(Char const (&arr)[length + 1]) noexcept + explicit(false) consteval StaticString(Char const (&arr)[length + 1u]) noexcept { - std::ranges::copy_n(arr, length, std::ranges::begin(data)); + std::ranges::copy(arr, data.begin()); } [[nodiscard]] static constexpr bool empty() noexcept { - return false; + return length == 0u; } [[nodiscard]] @@ -44,47 +48,35 @@ MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp::util [[nodiscard]] constexpr auto begin() const noexcept { - return std::ranges::begin(data); + return data.cbegin(); } [[nodiscard]] constexpr auto end() const noexcept { - return std::ranges::end(data); - } - }; - - template - class StaticString - { - public: - [[nodiscard]] // - explicit(false) consteval StaticString([[maybe_unused]] Char const (&arr)[1]) noexcept - { + return begin() + size(); } [[nodiscard]] - static constexpr bool empty() noexcept + constexpr std::basic_string_view view() const noexcept { - return true; + return {begin(), end()}; } [[nodiscard]] - static constexpr std::size_t size() noexcept + constexpr std::basic_string str() const noexcept { - return 0u; + return {begin(), end()}; } [[nodiscard]] - static constexpr Char const* begin() noexcept - { - return nullptr; - } + friend bool operator==(StaticString const&, StaticString const&) = default; + template > String> [[nodiscard]] - static constexpr Char const* end() noexcept + constexpr bool operator==(String const& other) const noexcept { - return nullptr; + return view() == std::basic_string_view{other}; } }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d4a83a2ab1..84da2356cc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright Dominic (DNKpp) Koepke 2024 - 2025. +# Copyright Dominic (DNKpp) Koepke 2024-2026. # 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) @@ -11,6 +11,10 @@ add_subdirectory(compile-errors) option(MIMICPP_ENABLE_UNIT_TESTS "Determines, whether the unit-tests shall be built." ON) if (MIMICPP_ENABLE_UNIT_TESTS) add_subdirectory(unit-tests) + + if (MIMICPP_CONFIG_EXPERIMENTAL_PRETTY_TYPES) + add_subdirectory(type-name-print-tests) + endif () endif () if (MIMICPP_CONFIG_EXPERIMENTAL_ENABLE_CXX20_MODULES__UNPORTABLE__) diff --git a/test/cmake/FindCatch2.cmake b/test/cmake/FindCatch2.cmake index 59a1764b81..a1216c0409 100644 --- a/test/cmake/FindCatch2.cmake +++ b/test/cmake/FindCatch2.cmake @@ -1,4 +1,4 @@ -# Copyright Dominic (DNKpp) Koepke 2024 - 2025. +# Copyright Dominic (DNKpp) Koepke 2024-2026. # 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) @@ -8,7 +8,7 @@ include(get_cpm) # fixes reporting in clion set(CATCH_CONFIG_CONSOLE_WIDTH 800 CACHE STRING "") -CPMAddPackage("gh:catchorg/Catch2@3.10.0") +CPMAddPackage("gh:catchorg/Catch2@3.14.0") if (Catch2_ADDED) include("${Catch2_SOURCE_DIR}/extras/Catch.cmake") diff --git a/test/compile-errors/CMakeLists.txt b/test/compile-errors/CMakeLists.txt index 241a7b1bb0..db546c06d3 100644 --- a/test/compile-errors/CMakeLists.txt +++ b/test/compile-errors/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright Dominic (DNKpp) Koepke 2024 - 2025. +# Copyright Dominic (DNKpp) Koepke 2024-2026. # 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) @@ -50,16 +50,9 @@ function(check_compile_error SOURCE_FILE) set_tests_properties(${TEST_NAME} PROPERTIES PASS_REGULAR_EXPRESSION "${EXPECTED_COMPILE_ERRORS}" + # Ensure that the build-processes do not interfere with each other. + RESOURCE_LOCK compile-error-test-lock ) - - # On msvc, these manual compiled tests should not run in parallel, because they seem to create some lock-files. - # error MSB3491: Could not write lines to file "[...]/ZERO_CHECK/ZERO_CHECK.tlog/ZERO_CHECK.lastbuildstate". - # The process cannot access the file '...' because it is being used by another process. - if (MSVC) - set_tests_properties(${TEST_NAME} PROPERTIES - RESOURCE_LOCK compile-error-test-lock - ) - endif () endfunction() check_compile_error("missing-finalizer-for-non-void.cpp") diff --git a/test/stacktrace-tests/custom/Printing.cpp b/test/stacktrace-tests/custom/Printing.cpp index 9d8aa148f6..8daa5638dc 100644 --- a/test/stacktrace-tests/custom/Printing.cpp +++ b/test/stacktrace-tests/custom/Printing.cpp @@ -1,4 +1,4 @@ -// Copyright Dominic (DNKpp) Koepke 2024 - 2025. +// Copyright Dominic (DNKpp) Koepke 2024-2026. // 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) @@ -8,7 +8,7 @@ using namespace mimicpp; TEST_CASE( - "Stacktrace is printable.", + "Custom stacktrace is printable.", "[stacktrace]") { using trompeloeil::_; diff --git a/test/type-name-print-tests/ArrayTypes.cpp b/test/type-name-print-tests/ArrayTypes.cpp new file mode 100644 index 0000000000..fa40d158f3 --- /dev/null +++ b/test/type-name-print-tests/ArrayTypes.cpp @@ -0,0 +1,71 @@ +// Copyright Dominic (DNKpp) Koepke 2026. +// 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 "Common.hpp" + +using namespace mimicpp; + +TEMPLATE_TEST_CASE( + "printing::type::prettify_type supports array types.", + "[print][print::type]", + testing::modifier_maker, + testing::modifier_maker, + testing::modifier_maker, + testing::modifier_maker) +{ + std::string const arrayMod = TestType::suffix.empty() + ? "" + : "\\(" + std::string{TestType::suffix} + "\\)"; + CAPTURE(arrayMod); + + std::ostringstream ss{}; + + SECTION("When an unbounded array type is given.") + { + using T = testing::mod_type_t; + std::string const rawName{printing::type::type_name()}; + CAPTURE(rawName); + + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + rawName); + + CHECK_THAT( + ss.str(), + Catch::Matchers::Matches("int" + arrayMod + R"(\[\])")); + } + + SECTION("When an bounded array type is given.") + { + using T = testing::mod_type_t; + std::string const rawName{printing::type::type_name()}; + CAPTURE(rawName); + + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + rawName); + + CHECK_THAT( + ss.str(), + Catch::Matchers::Matches("int" + arrayMod + R"(\[42\])")); + } + + SECTION("When multi-dim array is given.") + { + using T = testing::mod_type_t; + std::string const rawName{printing::type::type_name()}; + CAPTURE(rawName); + + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + rawName); + + CHECK_THAT( + ss.str(), + Catch::Matchers::Matches("int" + arrayMod + R"(\[\]\[42\]\[1337\])")); + } +} diff --git a/test/type-name-print-tests/BuiltinTypes.cpp b/test/type-name-print-tests/BuiltinTypes.cpp new file mode 100644 index 0000000000..02482bb275 --- /dev/null +++ b/test/type-name-print-tests/BuiltinTypes.cpp @@ -0,0 +1,169 @@ +// Copyright Dominic (DNKpp) Koepke 2026. +// 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 "Common.hpp" + +using namespace mimicpp; + +TEMPLATE_TEST_CASE_SIG( + "printing::type::prettify_type handles built-in integral types correctly.", + "[print][print::type]", + ((auto expected, typename T), expected, T), + (util::StaticString{"char"}, char), + (util::StaticString{"short"}, short), + (util::StaticString{"short"}, short int), + (util::StaticString{"int"}, int), + (util::StaticString{"long"}, long), + (util::StaticString{"long"}, long int), + (util::StaticString{"long long"}, long long), + (util::StaticString{"long long"}, long long int)) +{ + std::ostringstream ss{}; + + std::string const name{printing::type::type_name()}; + CAPTURE(name); + + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + name); + CHECK_THAT( + std::move(ss).str(), + Catch::Matchers::Matches(expected.str())); +} + +TEMPLATE_TEST_CASE_SIG( + "printing::type::prettify_type handles built-in signed integral types correctly.", + "[print][print::type]", + ((auto expected, typename T), expected, T), + (util::StaticString{"signed char"}, char), + (util::StaticString{"short"}, short), + (util::StaticString{"short"}, short int), + (util::StaticString{"int"}, int), + (util::StaticString{"long"}, long), + (util::StaticString{"long"}, long int), + (util::StaticString{"long long"}, long long), + (util::StaticString{"long long"}, long long int)) +{ + std::ostringstream ss{}; + + std::string const name{printing::type::type_name>()}; + CAPTURE(name); + + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + name); + CHECK_THAT( + std::move(ss).str(), + Catch::Matchers::Matches(expected.str())); +} + +TEMPLATE_TEST_CASE_SIG( + "printing::type::prettify_type handles built-in unsigned integral types correctly.", + "[print][print::type]", + ((auto expected, typename T), expected, T), + (util::StaticString{"unsigned char"}, char), + (util::StaticString{"unsigned short"}, short), + (util::StaticString{"unsigned short"}, short int), + (util::StaticString{"unsigned int"}, int), + (util::StaticString{"unsigned long"}, long), + (util::StaticString{"unsigned long"}, long int), + (util::StaticString{"unsigned long long"}, long long), + (util::StaticString{"unsigned long long"}, long long int)) +{ + std::ostringstream ss{}; + + std::string const name{printing::type::type_name>()}; + CAPTURE(name); + + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + name); + CHECK_THAT( + std::move(ss).str(), + Catch::Matchers::Matches(expected.str())); +} + +TEMPLATE_LIST_TEST_CASE( + "printing::type::prettify_type handles bool correctly.", + "[print][print::type]", + testing::common_mod_list) +{ + std::string const suffixPattern{TestType::suffix}; + CAPTURE(suffixPattern); + using T = testing::mod_type_t; + + std::string const name{printing::type::type_name()}; + CAPTURE(name); + + std::ostringstream ss{}; + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + name); + CHECK_THAT( + std::move(ss).str(), + Catch::Matchers::Matches("bool" + suffixPattern)); +} + +namespace +{ + template + [[nodiscard]] + std::pair make_case(std::string expected) + { + return { + std::move(expected), + std::string{printing::type::type_name()}}; + } +} + +TEMPLATE_LIST_TEST_CASE( + "printing::type::prettify_type handles built-in float types correctly.", + "[print][print::type]", + testing::common_mod_list) +{ + std::string const suffixPattern{TestType::suffix}; + CAPTURE(suffixPattern); + + auto const [expectedBase, rawName] = GENERATE( + (make_case>("float")), + (make_case>("double")), + (make_case>("long double"))); + CAPTURE(rawName); + + std::ostringstream ss{}; + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + rawName); + CHECK_THAT( + std::move(ss).str(), + Catch::Matchers::Matches(expectedBase + suffixPattern)); +} + +TEMPLATE_LIST_TEST_CASE( + "printing::type::prettify_type handles built-in character types correctly.", + "[print][print::type]", + testing::common_mod_list) +{ + std::string const suffixPattern{TestType::suffix}; + CAPTURE(suffixPattern); + + auto const [expectedBase, rawName] = GENERATE( + (make_case>("char")), + (make_case>("wchar_t")), + (make_case>("char8_t")), + (make_case>("char16_t")), + (make_case>("char32_t"))); + CAPTURE(rawName); + + std::ostringstream ss{}; + printing::type::prettify_type( + std::ostreambuf_iterator{ss}, + rawName); + CHECK_THAT( + std::move(ss).str(), + Catch::Matchers::Matches(expectedBase + suffixPattern)); +} diff --git a/test/type-name-print-tests/CMakeLists.txt b/test/type-name-print-tests/CMakeLists.txt new file mode 100644 index 0000000000..d089d0f1c5 --- /dev/null +++ b/test/type-name-print-tests/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright Dominic (DNKpp) Koepke 2026. +# 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) + +set(TARGET_NAME mimicpp-type-name-print-tests) + +add_executable(${TARGET_NAME} + "ArrayTypes.cpp" + "BuiltinTypes.cpp" + "FunctionIdentifiers.cpp" + "FunctionLocalTypes.cpp" + "FunctionPtrTypes.cpp" + "OperatorLocalTypes.cpp" + "SpecialTypes.cpp" + "TemplateTypes.cpp" +) + +include(Mimic++-EnableSanitizers) +enable_sanitizers(${TARGET_NAME}) + +find_package(Catch2 REQUIRED) +target_link_libraries(${TARGET_NAME} PRIVATE + mimicpp::header-only + mimicpp::test::basics + Catch2::Catch2WithMain +) + +target_precompile_headers(${TARGET_NAME} PRIVATE + + "../unit-tests/Catch2FallbackStringifier.hpp" + +) + +catch_discover_tests(${TARGET_NAME}) diff --git a/test/type-name-print-tests/Common.hpp b/test/type-name-print-tests/Common.hpp new file mode 100644 index 0000000000..b85ef610c2 --- /dev/null +++ b/test/type-name-print-tests/Common.hpp @@ -0,0 +1,115 @@ +// Copyright Dominic (DNKpp) Koepke 2026. +// 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) + +#pragma once + +#include "mimic++/utilities/StaticString.hpp" +#include "mimic++/utilities/TypeList.hpp" + +#include +#include + +namespace mimicpp::testing +{ + inline std::string const anonNsScopePattern = R"(\{anon-ns\}::)"; + inline std::string const anonTypePattern = ""; + inline std::string const lambdaPattern = ""; + + [[nodiscard, maybe_unused]] + inline std::string maybe_pattern(std::string const& pattern) + { + return "(:?" + pattern + ")?"; + } + + [[nodiscard, maybe_unused]] + std::string alternative_pattern(std::string const& first, auto const&... alts) + { + return "(:?" + first + ((std::string{"|"} + alts) + ...) + ")"; + } + + template + struct modifier + { + using type = T; + static constexpr std::string_view suffix = suffixText.view(); + }; + + namespace mods + { + template + using decayed = modifier; + + template + using add_lvalue_ref = modifier; + template + using add_const_lvalue_ref = modifier; + template + using add_volatile_lvalue_ref = modifier; + template + using add_cv_lvalue_ref = modifier; + + template + using add_rvalue_ref = modifier; + template + using add_const_rvalue_ref = modifier; + template + using add_volatile_rvalue_ref = modifier; + template + using add_cv_rvalue_ref = modifier; + + template + using add_pointer = modifier; + template + using add_const_pointer = modifier; + } + + template