198 lines
6.7 KiB
C++
198 lines
6.7 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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 <cstdio>
|
|
#include <string>
|
|
|
|
#include "assert_macros.h"
|
|
#include "test_macros.h"
|
|
|
|
#ifndef TEST_HAS_NO_LOCALIZATION
|
|
# include <concepts>
|
|
# include <iterator>
|
|
# include <sstream>
|
|
#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 <class InIt, class OutIt>
|
|
OutIt test_transcode() = delete;
|
|
|
|
template <class InIt, class OutIt>
|
|
requires(std::output_iterator<OutIt, const char&> && std::same_as<std::iter_value_t<InIt>, char8_t>)
|
|
OutIt test_transcode(InIt first, InIt last, OutIt out_it) {
|
|
return std::copy(first, last, out_it);
|
|
}
|
|
|
|
template <class OutIt>
|
|
requires std::output_iterator<OutIt, const char&>
|
|
void test_encode(OutIt& out_it, char16_t value) {
|
|
if (value < 0x80)
|
|
*out_it++ = static_cast<char>(value);
|
|
else if (value < 0x800) {
|
|
*out_it++ = static_cast<char>(0b11000000 | (value >> 6));
|
|
*out_it++ = static_cast<char>(0b10000000 | (value & 0b00111111));
|
|
} else {
|
|
*out_it++ = static_cast<char>(0b11100000 | (value >> 12));
|
|
*out_it++ = static_cast<char>(0b10000000 | ((value) >> 6 & 0b00111111));
|
|
*out_it++ = static_cast<char>(0b10000000 | (value & 0b00111111));
|
|
}
|
|
}
|
|
|
|
template <class OutIt>
|
|
requires std::output_iterator<OutIt, const char&>
|
|
void test_encode(OutIt& out_it, char32_t value) {
|
|
if ((value & 0xffff0000) == 0)
|
|
test_encode(out_it, static_cast<char16_t>(value));
|
|
else {
|
|
*out_it++ = static_cast<char>(0b11100000 | (value >> 18));
|
|
*out_it++ = static_cast<char>(0b10000000 | ((value) >> 12 & 0b00111111));
|
|
*out_it++ = static_cast<char>(0b10000000 | ((value) >> 6 & 0b00111111));
|
|
*out_it++ = static_cast<char>(0b10000000 | (value & 0b00111111));
|
|
}
|
|
}
|
|
|
|
template <class InIt, class OutIt>
|
|
requires(std::output_iterator<OutIt, const char&> &&
|
|
(std::same_as<std::iter_value_t<InIt>, char16_t>
|
|
# ifndef TEST_HAS_NO_WIDE_CHARACTERS
|
|
|| (std::same_as<std::iter_value_t<InIt>, 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<char16_t>(test_replacement_character));
|
|
continue;
|
|
}
|
|
|
|
if (!test_is_high_surrogate(value)) {
|
|
test_encode(out_it, static_cast<char16_t>(value));
|
|
continue;
|
|
}
|
|
|
|
if (first == last || !test_is_low_surrogate(static_cast<char32_t>(*first))) [[unlikely]] {
|
|
test_encode(out_it, static_cast<char16_t>(test_replacement_character));
|
|
continue;
|
|
}
|
|
|
|
value -= 0xd800;
|
|
value <<= 10;
|
|
value += static_cast<char32_t>(*first++) - 0xdc00;
|
|
value += 0x10000;
|
|
|
|
if (test_is_code_point(value)) [[likely]]
|
|
test_encode(out_it, value);
|
|
else
|
|
test_encode(out_it, static_cast<char16_t>(test_replacement_character));
|
|
}
|
|
|
|
return out_it;
|
|
}
|
|
|
|
template <class InIt, class OutIt>
|
|
requires(std::output_iterator<OutIt, const char&> &&
|
|
(std::same_as<std::iter_value_t<InIt>, char32_t>
|
|
# ifndef TEST_HAS_NO_WIDE_CHARACTERS
|
|
|| (std::same_as<std::iter_value_t<InIt>, 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<char16_t>(test_replacement_character));
|
|
}
|
|
return out_it;
|
|
}
|
|
|
|
template <class T>
|
|
concept test_streamable = requires(std::stringstream& stream, T&& value) { stream << value; };
|
|
|
|
template <class R>
|
|
concept test_convertable_range = (!test_streamable<R> && requires(R&& value) {
|
|
std::basic_string_view{std::begin(value), std::end(value)};
|
|
});
|
|
|
|
template <class T>
|
|
concept test_can_concat = test_streamable<T> || test_convertable_range<T>;
|
|
|
|
template <test_streamable T>
|
|
std::ostream& test_concat(std::ostream& stream, T&& value) {
|
|
return stream << value;
|
|
}
|
|
|
|
template <test_convertable_range T>
|
|
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<std::remove_cvref_t<T>>) {
|
|
--e;
|
|
}
|
|
test_transcode(b, e, std::ostream_iterator<char>{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 <class... Args>
|
|
std::string test_concat_message([[maybe_unused]] Args&&... args) {
|
|
# ifndef TEST_HAS_NO_LOCALIZATION
|
|
if constexpr ((test_can_concat<Args> && ...)) {
|
|
std::stringstream sstr;
|
|
((test_concat(sstr, std::forward<Args>(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
|