xref: /llvm-project/libcxx/test/support/format.functions.common.h (revision 09e3a360581dc36d0820d3fb6da9bd7cfed87b5d)
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_FORMAT_FUNCTIONS_COMMON_H
10 #define TEST_SUPPORT_FORMAT_FUNCTIONS_COMMON_H
11 
12 // Contains the common part of the formatter tests for different papers.
13 
14 #include <algorithm>
15 #include <cctype>
16 #include <charconv>
17 #include <cstddef>
18 #include <cstdint>
19 #include <cstdlib>
20 #include <format>
21 #include <ranges>
22 #include <string_view>
23 #include <string>
24 #include <vector>
25 
26 #include "make_string.h"
27 
28 #define STR(S) MAKE_STRING(CharT, S)
29 #define SV(S) MAKE_STRING_VIEW(CharT, S)
30 #define CSTR(S) MAKE_CSTRING(CharT, S)
31 
32 template <class T>
33 struct context {};
34 
35 template <>
36 struct context<char> {
37   using type = std::format_context;
38 };
39 
40 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
41 template <>
42 struct context<wchar_t> {
43   using type = std::wformat_context;
44 };
45 #endif
46 
47 template <class T>
48 using context_t = typename context<T>::type;
49 
50 // A user-defined type used to test the handle formatter.
51 enum class status : std::uint16_t { foo = 0xAAAA, bar = 0x5555, foobar = 0xAA55 };
52 
53 // The formatter for a user-defined type used to test the handle formatter.
54 template <class CharT>
55 struct std::formatter<status, CharT> {
56   // During the 2023 Issaquah meeting LEWG made it clear a formatter is
57   // required to call its parse function. LWG3892 Adds the wording for that
58   // requirement. Therefore this formatter is initialized in an invalid state.
59   // A call to parse sets it in a valid state and a call to format validates
60   // the state.
61   int type = -1;
62 
63   constexpr auto parse(basic_format_parse_context<CharT>& parse_ctx) -> decltype(parse_ctx.begin()) {
64     auto begin = parse_ctx.begin();
65     auto end   = parse_ctx.end();
66     type       = 0;
67     if (begin == end)
68       return begin;
69 
70     switch (*begin) {
71     case CharT('x'):
72       break;
73     case CharT('X'):
74       type = 1;
75       break;
76     case CharT('s'):
77       type = 2;
78       break;
79     case CharT('}'):
80       return begin;
81     default:
82       throw_format_error("The type option contains an invalid value for a status formatting argument");
83     }
84 
85     ++begin;
86     if (begin != end && *begin != CharT('}'))
87       throw_format_error("The format specifier should consume the input or end with a '}'");
88 
89     return begin;
90   }
91 
92   template <class Out>
93   auto format(status s, basic_format_context<Out, CharT>& ctx) const -> decltype(ctx.out()) {
94     const char* names[] = {"foo", "bar", "foobar"};
95     char buffer[7];
96     const char* begin = names[0];
97     const char* end = names[0];
98     switch (type) {
99     case -1:
100       throw_format_error("The formatter's parse function has not been called.");
101 
102     case 0:
103       begin = buffer;
104       buffer[0] = '0';
105       buffer[1] = 'x';
106       end = std::to_chars(&buffer[2], std::end(buffer), static_cast<std::uint16_t>(s), 16).ptr;
107       buffer[6] = '\0';
108       break;
109 
110     case 1:
111       begin = buffer;
112       buffer[0] = '0';
113       buffer[1] = 'X';
114       end = std::to_chars(&buffer[2], std::end(buffer), static_cast<std::uint16_t>(s), 16).ptr;
115       std::transform(static_cast<const char*>(&buffer[2]), end, &buffer[2], [](char c) {
116         return static_cast<char>(std::toupper(c)); });
117       buffer[6] = '\0';
118       break;
119 
120     case 2:
121       switch (s) {
122       case status::foo:
123         begin = names[0];
124         break;
125       case status::bar:
126         begin = names[1];
127         break;
128       case status::foobar:
129         begin = names[2];
130         break;
131       }
132       end = begin + strlen(begin);
133       break;
134     }
135 
136     return std::copy(begin, end, ctx.out());
137   }
138 
139 private:
140   [[noreturn]] void throw_format_error([[maybe_unused]] const char* s) const {
141 #ifndef TEST_HAS_NO_EXCEPTIONS
142     throw std::format_error(s);
143 #else
144     std::abort();
145 #endif
146   }
147 };
148 
149 struct parse_call_validator {
150   struct parse_function_not_called {};
151 
152   friend constexpr auto operator<=>(const parse_call_validator& lhs, const parse_call_validator& rhs) {
153     return &lhs <=> &rhs;
154   }
155 };
156 
157 // The formatter for a user-defined type used to test the handle formatter.
158 //
159 // Like std::formatter<status, CharT> this formatter validates that parse is
160 // called. This formatter is intended to be used when the formatter's parse is
161 // called directly and not with format. In that case the format-spec does not
162 // require a terminating }. The tests must be written in a fashion where this
163 // formatter is always called with an empty format-spec. This requirement
164 // allows testing of certain code paths that are never reached by using a
165 // well-formed format-string in the format functions.
166 template <class CharT>
167 struct std::formatter<parse_call_validator, CharT> {
168   bool parse_called{false};
169 
170   constexpr auto parse(basic_format_parse_context<CharT>& parse_ctx) -> decltype(parse_ctx.begin()) {
171     auto begin = parse_ctx.begin();
172     auto end   = parse_ctx.end();
173     assert(begin == end);
174     parse_called = true;
175     return begin;
176   }
177 
178   auto format(parse_call_validator, auto& ctx) const -> decltype(ctx.out()) {
179     if (!parse_called)
180       throw_error<parse_call_validator::parse_function_not_called>();
181     return ctx.out();
182   }
183 
184 private:
185   template <class T>
186   [[noreturn]] void throw_error() const {
187 #ifndef TEST_HAS_NO_EXCEPTIONS
188     throw T{};
189 #else
190     std::abort();
191 #endif
192   }
193 };
194 
195 // Creates format string for the invalid types.
196 //
197 // valid contains a list of types that are valid.
198 // - The type ?s is the only type requiring 2 characters, use S for that type.
199 // - Whether n is a type or not depends on the context, is is always used.
200 //
201 // The return value is a collection of basic_strings, instead of
202 // basic_string_views since the values are temporaries.
203 namespace detail {
204 template <class CharT, std::size_t N>
205 std::basic_string<CharT> get_colons() {
206   static std::basic_string<CharT> result(N, CharT(':'));
207   return result;
208 }
209 
210 constexpr std::string_view get_format_types() {
211   return "aAbBcdeEfFgGopPsxX"
212 #if TEST_STD_VER > 20
213          "?"
214 #endif
215       ;
216 }
217 
218 template <class CharT, /*format_types types,*/ size_t N>
219 std::vector<std::basic_string<CharT>> fmt_invalid_types(std::string_view valid) {
220   // std::ranges::to is not available in C++20.
221   std::vector<std::basic_string<CharT>> result;
222   std::ranges::copy(
223       get_format_types() | std::views::filter([&](char type) { return valid.find(type) == std::string_view::npos; }) |
224           std::views::transform([&](char type) { return std::format(SV("{{{}{}}}"), get_colons<CharT, N>(), type); }),
225       std::back_inserter(result));
226   return result;
227 }
228 
229 } // namespace detail
230 
231 // Creates format string for the invalid types.
232 //
233 // valid contains a list of types that are valid.
234 //
235 // The return value is a collection of basic_strings, instead of
236 // basic_string_views since the values are temporaries.
237 template <class CharT>
238 std::vector<std::basic_string<CharT>> fmt_invalid_types(std::string_view valid) {
239   return detail::fmt_invalid_types<CharT, 1>(valid);
240 }
241 
242 // Like fmt_invalid_types but when the format spec is for an underlying formatter.
243 template <class CharT>
244 std::vector<std::basic_string<CharT>> fmt_invalid_nested_types(std::string_view valid) {
245   return detail::fmt_invalid_types<CharT, 2>(valid);
246 }
247 
248 #endif // TEST_SUPPORT_FORMAT_FUNCTIONS_COMMON_H
249