xref: /llvm-project/libcxx/test/support/concat_macros.h (revision 774295ca1d5ff752cb478b61f22a5b1dbe33074f)
1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef TEST_SUPPORT_CONCAT_MACROS_H
10 #define TEST_SUPPORT_CONCAT_MACROS_H
11 
12 #include <cstdio>
13 #include <string>
14 
15 #include "assert_macros.h"
16 #include "test_macros.h"
17 
18 #ifndef TEST_HAS_NO_LOCALIZATION
19 #  include <concepts>
20 #  include <iterator>
21 #  include <sstream>
22 #endif
23 
24 #if TEST_STD_VER > 17
25 
26 #  ifndef TEST_HAS_NO_LOCALIZATION
27 
test_is_high_surrogate(char32_t value)28 [[nodiscard]] constexpr bool test_is_high_surrogate(char32_t value) { return value >= 0xd800 && value <= 0xdbff; }
29 
test_is_low_surrogate(char32_t value)30 [[nodiscard]] constexpr bool test_is_low_surrogate(char32_t value) { return value >= 0xdc00 && value <= 0xdfff; }
31 
test_is_surrogate(char32_t value)32 [[nodiscard]] constexpr bool test_is_surrogate(char32_t value) { return value >= 0xd800 && value <= 0xdfff; }
33 
test_is_code_point(char32_t value)34 [[nodiscard]] constexpr bool test_is_code_point(char32_t value) { return value <= 0x10ffff; }
35 
test_is_scalar_value(char32_t value)36 [[nodiscard]] constexpr bool test_is_scalar_value(char32_t value) {
37   return test_is_code_point(value) && !test_is_surrogate(value);
38 }
39 
40 inline constexpr char32_t test_replacement_character = U'\ufffd';
41 
42 template <class InIt, class OutIt>
43 OutIt test_transcode() = delete;
44 
45 template <class InIt, class OutIt>
requires(std::output_iterator<OutIt,const char &> && std::same_as<std::iter_value_t<InIt>,char8_t>)46   requires(std::output_iterator<OutIt, const char&> && std::same_as<std::iter_value_t<InIt>, char8_t>)
47 OutIt test_transcode(InIt first, InIt last, OutIt out_it) {
48   return std::copy(first, last, out_it);
49 }
50 
51 template <class OutIt>
52   requires std::output_iterator<OutIt, const char&>
test_encode(OutIt & out_it,char16_t value)53 void test_encode(OutIt& out_it, char16_t value) {
54   if (value < 0x80)
55     *out_it++ = static_cast<char>(value);
56   else if (value < 0x800) {
57     *out_it++ = static_cast<char>(0b11000000 | (value >> 6));
58     *out_it++ = static_cast<char>(0b10000000 | (value & 0b00111111));
59   } else {
60     *out_it++ = static_cast<char>(0b11100000 | (value >> 12));
61     *out_it++ = static_cast<char>(0b10000000 | ((value) >> 6 & 0b00111111));
62     *out_it++ = static_cast<char>(0b10000000 | (value & 0b00111111));
63   }
64 }
65 
66 template <class OutIt>
67   requires std::output_iterator<OutIt, const char&>
test_encode(OutIt & out_it,char32_t value)68 void test_encode(OutIt& out_it, char32_t value) {
69   if ((value & 0xffff0000) == 0)
70     test_encode(out_it, static_cast<char16_t>(value));
71   else {
72     *out_it++ = static_cast<char>(0b11100000 | (value >> 18));
73     *out_it++ = static_cast<char>(0b10000000 | ((value) >> 12 & 0b00111111));
74     *out_it++ = static_cast<char>(0b10000000 | ((value) >> 6 & 0b00111111));
75     *out_it++ = static_cast<char>(0b10000000 | (value & 0b00111111));
76   }
77 }
78 
79 template <class InIt, class OutIt>
80   requires(std::output_iterator<OutIt, const char&> &&
81            (std::same_as<std::iter_value_t<InIt>, char16_t>
82 #    ifndef TEST_HAS_NO_WIDE_CHARACTERS
83             || (std::same_as<std::iter_value_t<InIt>, wchar_t> && sizeof(wchar_t) == 2)
84 #    endif
85                 ))
test_transcode(InIt first,InIt last,OutIt out_it)86 OutIt test_transcode(InIt first, InIt last, OutIt out_it) {
87   while (first != last) {
88     char32_t value = *first++;
89 
90     if (test_is_low_surrogate(value)) [[unlikely]] {
91       test_encode(out_it, static_cast<char16_t>(test_replacement_character));
92       continue;
93     }
94 
95     if (!test_is_high_surrogate(value)) {
96       test_encode(out_it, static_cast<char16_t>(value));
97       continue;
98     }
99 
100     if (first == last || !test_is_low_surrogate(static_cast<char32_t>(*first))) [[unlikely]] {
101       test_encode(out_it, static_cast<char16_t>(test_replacement_character));
102       continue;
103     }
104 
105     value -= 0xd800;
106     value <<= 10;
107     value += static_cast<char32_t>(*first++) - 0xdc00;
108     value += 0x10000;
109 
110     if (test_is_code_point(value)) [[likely]]
111       test_encode(out_it, value);
112     else
113       test_encode(out_it, static_cast<char16_t>(test_replacement_character));
114   }
115 
116   return out_it;
117 }
118 
119 template <class InIt, class OutIt>
120   requires(std::output_iterator<OutIt, const char&> &&
121            (std::same_as<std::iter_value_t<InIt>, char32_t>
122 #    ifndef TEST_HAS_NO_WIDE_CHARACTERS
123             || (std::same_as<std::iter_value_t<InIt>, wchar_t> && sizeof(wchar_t) == 4)
124 #    endif
125                 ))
test_transcode(InIt first,InIt last,OutIt out_it)126 OutIt test_transcode(InIt first, InIt last, OutIt out_it) {
127   while (first != last) {
128     char32_t value = *first++;
129     if (test_is_code_point(value)) [[likely]]
130       test_encode(out_it, value);
131     else
132       test_encode(out_it, static_cast<char16_t>(test_replacement_character));
133   }
134   return out_it;
135 }
136 
137 template <class T>
requires(std::stringstream & stream,T && value)138 concept test_streamable = requires(std::stringstream& stream, T&& value) { stream << value; };
139 
140 template <class R>
141 concept test_convertable_range = (!test_streamable<R> && requires(R&& value) {
142   std::basic_string_view{std::begin(value), std::end(value)};
143 });
144 
145 template <class T>
146 concept test_can_concat = test_streamable<T> || test_convertable_range<T>;
147 
148 template <test_streamable T>
test_concat(std::ostream & stream,T && value)149 std::ostream& test_concat(std::ostream& stream, T&& value) {
150   return stream << value;
151 }
152 
153 template <test_convertable_range T>
test_concat(std::ostream & stream,T && value)154 std::ostream& test_concat(std::ostream& stream, T&& value) {
155   auto b = std::begin(value);
156   auto e = std::end(value);
157   if (b != e) {
158     // When T is an array it's string-literal, remove the NUL terminator.
159     if constexpr (std::is_array_v<std::remove_cvref_t<T>>) {
160       --e;
161     }
162     test_transcode(b, e, std::ostream_iterator<char>{stream});
163   }
164   return stream;
165 }
166 #  endif // TEST_HAS_NO_LOCALIZATION
167 
168 // If possible concatenates message for the assertion function, else returns a
169 // default message. Not being able to stream is not considered an error. For
170 // example, streaming to std::wcerr doesn't work properly in the CI. Therefore
171 // the formatting tests should only stream to std::string.
172 //
173 // The macro TEST_WRITE_CONCATENATED can be used to evaluate the arguments
174 // lazily. This useful when using this function in combination with
175 // assert_macros.h.
176 template <class... Args>
test_concat_message(Args &&...args)177 std::string test_concat_message([[maybe_unused]] Args&&... args) {
178 #  ifndef TEST_HAS_NO_LOCALIZATION
179   if constexpr ((test_can_concat<Args> && ...)) {
180     std::stringstream sstr;
181     ((test_concat(sstr, std::forward<Args>(args))), ...);
182     return sstr.str();
183   } else
184 #  endif // TEST_HAS_NO_LOCALIZATION
185     return "Message discarded since it can't be streamed to std::cerr.\n";
186 }
187 
188 // Writes its arguments to stderr, using the test_concat_message helper.
189 #  define TEST_WRITE_CONCATENATED(...) [&] { ::test_eprintf("%s", ::test_concat_message(__VA_ARGS__).c_str()); }
190 
191 #else
192 
193 // Fallback definition before C++20 that allows using the macro but doesn't provide a very good message.
194 #  define TEST_WRITE_CONCATENATED(...) [&] { ::test_eprintf("%s", TEST_STRINGIZE(__VA_ARGS__)); }
195 
196 #endif // TEST_STD_VER > 17
197 
198 #endif //  TEST_SUPPORT_CONCAT_MACROS_H
199