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