//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef TEST_SUPPORT_CONCAT_MACROS_H #define TEST_SUPPORT_CONCAT_MACROS_H #include #include #include "assert_macros.h" #include "test_macros.h" #ifndef TEST_HAS_NO_LOCALIZATION # include # include # include #endif #if TEST_STD_VER > 17 # ifndef TEST_HAS_NO_LOCALIZATION [[nodiscard]] constexpr bool test_is_high_surrogate(char32_t value) { return value >= 0xd800 && value <= 0xdbff; } [[nodiscard]] constexpr bool test_is_low_surrogate(char32_t value) { return value >= 0xdc00 && value <= 0xdfff; } [[nodiscard]] constexpr bool test_is_surrogate(char32_t value) { return value >= 0xd800 && value <= 0xdfff; } [[nodiscard]] constexpr bool test_is_code_point(char32_t value) { return value <= 0x10ffff; } [[nodiscard]] constexpr bool test_is_scalar_value(char32_t value) { return test_is_code_point(value) && !test_is_surrogate(value); } inline constexpr char32_t test_replacement_character = U'\ufffd'; template OutIt test_transcode() = delete; template requires(std::output_iterator && std::same_as, char8_t>) OutIt test_transcode(InIt first, InIt last, OutIt out_it) { return std::copy(first, last, out_it); } template requires std::output_iterator void test_encode(OutIt& out_it, char16_t value) { if (value < 0x80) *out_it++ = static_cast(value); else if (value < 0x800) { *out_it++ = static_cast(0b11000000 | (value >> 6)); *out_it++ = static_cast(0b10000000 | (value & 0b00111111)); } else { *out_it++ = static_cast(0b11100000 | (value >> 12)); *out_it++ = static_cast(0b10000000 | ((value) >> 6 & 0b00111111)); *out_it++ = static_cast(0b10000000 | (value & 0b00111111)); } } template requires std::output_iterator void test_encode(OutIt& out_it, char32_t value) { if ((value & 0xffff0000) == 0) test_encode(out_it, static_cast(value)); else { *out_it++ = static_cast(0b11100000 | (value >> 18)); *out_it++ = static_cast(0b10000000 | ((value) >> 12 & 0b00111111)); *out_it++ = static_cast(0b10000000 | ((value) >> 6 & 0b00111111)); *out_it++ = static_cast(0b10000000 | (value & 0b00111111)); } } template requires(std::output_iterator && (std::same_as, char16_t> # ifndef TEST_HAS_NO_WIDE_CHARACTERS || (std::same_as, wchar_t> && sizeof(wchar_t) == 2) # endif )) OutIt test_transcode(InIt first, InIt last, OutIt out_it) { while (first != last) { char32_t value = *first++; if (test_is_low_surrogate(value)) [[unlikely]] { test_encode(out_it, static_cast(test_replacement_character)); continue; } if (!test_is_high_surrogate(value)) { test_encode(out_it, static_cast(value)); continue; } if (first == last || !test_is_low_surrogate(static_cast(*first))) [[unlikely]] { test_encode(out_it, static_cast(test_replacement_character)); continue; } value -= 0xd800; value <<= 10; value += static_cast(*first++) - 0xdc00; value += 0x10000; if (test_is_code_point(value)) [[likely]] test_encode(out_it, value); else test_encode(out_it, static_cast(test_replacement_character)); } return out_it; } template requires(std::output_iterator && (std::same_as, char32_t> # ifndef TEST_HAS_NO_WIDE_CHARACTERS || (std::same_as, wchar_t> && sizeof(wchar_t) == 4) # endif )) OutIt test_transcode(InIt first, InIt last, OutIt out_it) { while (first != last) { char32_t value = *first++; if (test_is_code_point(value)) [[likely]] test_encode(out_it, value); else test_encode(out_it, static_cast(test_replacement_character)); } return out_it; } template concept test_streamable = requires(std::stringstream& stream, T&& value) { stream << value; }; template concept test_convertable_range = (!test_streamable && requires(R&& value) { std::basic_string_view{std::begin(value), std::end(value)}; }); template concept test_can_concat = test_streamable || test_convertable_range; template std::ostream& test_concat(std::ostream& stream, T&& value) { return stream << value; } template std::ostream& test_concat(std::ostream& stream, T&& value) { auto b = std::begin(value); auto e = std::end(value); if (b != e) { // When T is an array it's string-literal, remove the NUL terminator. if constexpr (std::is_array_v>) { --e; } test_transcode(b, e, std::ostream_iterator{stream}); } return stream; } # endif // TEST_HAS_NO_LOCALIZATION // If possible concatenates message for the assertion function, else returns a // default message. Not being able to stream is not considered an error. For // example, streaming to std::wcerr doesn't work properly in the CI. Therefore // the formatting tests should only stream to std::string. // // The macro TEST_WRITE_CONCATENATED can be used to evaluate the arguments // lazily. This useful when using this function in combination with // assert_macros.h. template std::string test_concat_message([[maybe_unused]] Args&&... args) { # ifndef TEST_HAS_NO_LOCALIZATION if constexpr ((test_can_concat && ...)) { std::stringstream sstr; ((test_concat(sstr, std::forward(args))), ...); return sstr.str(); } else # endif // TEST_HAS_NO_LOCALIZATION return "Message discarded since it can't be streamed to std::cerr.\n"; } // Writes its arguments to stderr, using the test_concat_message helper. # define TEST_WRITE_CONCATENATED(...) [&] { ::test_eprintf("%s", ::test_concat_message(__VA_ARGS__).c_str()); } #else // Fallback definition before C++20 that allows using the macro but doesn't provide a very good message. # define TEST_WRITE_CONCATENATED(...) [&] { ::test_eprintf("%s", TEST_STRINGIZE(__VA_ARGS__)); } #endif // TEST_STD_VER > 17 #endif // TEST_SUPPORT_CONCAT_MACROS_H