1 /* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
2 
3 #ifndef MPT_FORMAT_MESSAGE_HPP
4 #define MPT_FORMAT_MESSAGE_HPP
5 
6 
7 
8 #include "mpt/base/macros.hpp"
9 #include "mpt/base/namespace.hpp"
10 #include "mpt/base/span.hpp"
11 #include "mpt/string/types.hpp"
12 #include "mpt/string/utility.hpp"
13 
14 #include <array>
15 #include <stdexcept>
16 #include <utility>
17 
18 #include <cstddef>
19 
20 
21 
22 namespace mpt {
23 inline namespace MPT_INLINE_NS {
24 
25 
26 
27 class format_message_syntax_error
28 	: public std::domain_error {
29 public:
format_message_syntax_error()30 	format_message_syntax_error()
31 		: std::domain_error("format string syntax error") {
32 		return;
33 	}
34 };
35 
36 template <typename Tformatter, typename Tformat>
37 class message_formatter {
38 
39 public:
40 	using Tstring = typename mpt::make_string_type<Tformat>::type;
41 
42 private:
43 	Tstring format;
44 
45 private:
do_format(const mpt::span<const Tstring> vals) const46 	MPT_NOINLINE Tstring do_format(const mpt::span<const Tstring> vals) const {
47 		using traits = typename mpt::string_traits<Tstring>;
48 		using char_type = typename traits::char_type;
49 		using size_type = typename traits::size_type;
50 		Tstring result;
51 		const size_type len = traits::length(format);
52 		traits::reserve(result, len);
53 		std::size_t max_arg = 0;
54 		//std::size_t args = 0;
55 		bool success = true;
56 		enum class state : int
57 		{
58 			error = -1,
59 			text = 0,
60 			open_seen = 1,
61 			number_seen = 2,
62 			close_seen = 3,
63 		};
64 		state state = state::text;
65 		bool numbered_args = false;
66 		bool unnumbered_args = false;
67 		std::size_t last_arg = 0;
68 		std::size_t this_arg = 0;
69 		std::size_t current_arg = 0;
70 		for (size_type pos = 0; pos != len; ++pos) {
71 			char_type c = format[pos];
72 			switch (state) {
73 				case state::text:
74 					if (c == char_type('{')) {
75 						state = state::open_seen;
76 					} else if (c == char_type('}')) {
77 						state = state::close_seen;
78 					} else {
79 						state = state::text;
80 						traits::append(result, 1, c); // output c here
81 					}
82 					break;
83 				case state::open_seen:
84 					if (c == char_type('{')) {
85 						state = state::text;
86 						traits::append(result, 1, char_type('{')); // output { here
87 					} else if (c == char_type('}')) {
88 						state = state::text;
89 						unnumbered_args = true;
90 						last_arg++;
91 						this_arg = last_arg;
92 						{ // output this_arg here
93 							const std::size_t n = this_arg - 1;
94 							if (n < std::size(vals)) {
95 								traits::append(result, vals[n]);
96 							}
97 						}
98 						if (this_arg > max_arg) {
99 							max_arg = this_arg;
100 						}
101 						//args += 1;
102 					} else if (char_type('0') <= c && c <= char_type('9')) {
103 						state = state::number_seen;
104 						numbered_args = true;
105 						current_arg = c - char_type('0');
106 					} else {
107 						state = state::error;
108 					}
109 					break;
110 				case state::number_seen:
111 					if (c == char_type('{')) {
112 						state = state::error;
113 					} else if (c == char_type('}')) {
114 						state = state::text;
115 						this_arg = current_arg + 1;
116 						{ // output this_arg here
117 							const std::size_t n = this_arg - 1;
118 							if (n < std::size(vals)) {
119 								traits::append(result, vals[n]);
120 							}
121 						}
122 						if (this_arg > max_arg) {
123 							max_arg = this_arg;
124 						}
125 						//args += 1;
126 					} else if (char_type('0') <= c && c <= char_type('9')) {
127 						state = state::number_seen;
128 						numbered_args = true;
129 						current_arg = (current_arg * 10) + (c - char_type('0'));
130 					} else {
131 						state = state::error;
132 					}
133 					break;
134 				case state::close_seen:
135 					if (c == char_type('{')) {
136 						state = state::error;
137 					} else if (c == char_type('}')) {
138 						state = state::text;
139 						traits::append(result, 1, char_type('}')); // output } here
140 					} else {
141 						state = state::error;
142 					}
143 					break;
144 				case state::error:
145 					state = state::error;
146 					break;
147 			}
148 		}
149 		if (state == state::error) {
150 			success = false;
151 		}
152 		if (state != state::text) {
153 			success = false;
154 		}
155 		if (numbered_args && unnumbered_args) {
156 			success = false;
157 		}
158 		if (!success) {
159 			throw format_message_syntax_error();
160 		}
161 		return result;
162 	}
163 
164 public:
message_formatter(Tstring format_)165 	MPT_FORCEINLINE message_formatter(Tstring format_)
166 		: format(std::move(format_)) {
167 	}
168 
169 public:
170 	template <typename... Ts>
operator ()(Ts &&...xs) const171 	MPT_NOINLINE Tstring operator()(Ts &&... xs) const {
172 		const std::array<Tstring, sizeof...(xs)> vals{{Tformatter::template format<Tstring>(std::forward<Ts>(xs))...}};
173 		return do_format(mpt::as_span(vals));
174 	}
175 
176 }; // struct message_formatter<Tformat>
177 
178 
179 template <typename Tformatter, std::ptrdiff_t N, typename Tchar, typename Tstring>
180 class message_formatter_counted {
181 
182 private:
183 	message_formatter<Tformatter, Tstring> formatter;
184 
185 public:
186 	template <std::size_t literal_length>
message_formatter_counted(const Tchar (& format)[literal_length])187 	inline message_formatter_counted(const Tchar (&format)[literal_length])
188 		: formatter(Tstring(format)) {
189 		return;
190 	}
191 
192 public:
193 	template <typename... Ts>
operator ()(Ts &&...xs) const194 	inline Tstring operator()(Ts &&... xs) const {
195 		static_assert(static_cast<std::ptrdiff_t>(sizeof...(xs)) == N);
196 		return formatter(std::forward<Ts>(xs)...);
197 	}
198 
199 }; // struct message_formatter_counted<Tformat>
200 
201 
202 template <typename Tchar>
203 MPT_CONSTEXPRINLINE std::ptrdiff_t parse_format_string_argument_count_impl(const Tchar * const format, const std::size_t len) {
204 	std::size_t max_arg = 0;
205 	std::size_t args = 0;
206 	bool success = true;
207 	enum class state : int
208 	{
209 		error = -1,
210 		text = 0,
211 		open_seen = 1,
212 		number_seen = 2,
213 		close_seen = 3,
214 	};
215 	state state = state::text;
216 	bool numbered_args = false;
217 	bool unnumbered_args = false;
218 	std::size_t last_arg = 0;
219 	std::size_t this_arg = 0;
220 	std::size_t current_arg = 0;
221 	for (std::size_t pos = 0; pos != len; ++pos) {
222 		Tchar c = format[pos];
223 		switch (state) {
224 			case state::text:
225 				if (c == Tchar('{')) {
226 					state = state::open_seen;
227 				} else if (c == Tchar('}')) {
228 					state = state::close_seen;
229 				} else {
230 					state = state::text;
231 					// output c here
232 				}
233 				break;
234 			case state::open_seen:
235 				if (c == Tchar('{')) {
236 					state = state::text;
237 					// output { here
238 				} else if (c == Tchar('}')) {
239 					state = state::text;
240 					unnumbered_args = true;
241 					last_arg++;
242 					this_arg = last_arg;
243 					// output this_arg here
244 					if (this_arg > max_arg)
245 					{
246 						max_arg = this_arg;
247 					}
248 					args += 1;
249 				} else if (Tchar('0') <= c && c <= Tchar('9')) {
250 					state = state::number_seen;
251 					numbered_args = true;
252 					current_arg = c - Tchar('0');
253 				} else {
254 					state = state::error;
255 				}
256 				break;
257 			case state::number_seen:
258 				if (c == Tchar('{')) {
259 					state = state::error;
260 				} else if (c == Tchar('}')) {
261 					state = state::text;
262 					this_arg = current_arg + 1;
263 					// output this_arg here
264 					if (this_arg > max_arg) {
265 						max_arg = this_arg;
266 					}
267 					args += 1;
268 				} else if (Tchar('0') <= c && c <= Tchar('9')) {
269 					state = state::number_seen;
270 					numbered_args = true;
271 					current_arg = (current_arg * 10) + (c - Tchar('0'));
272 				} else {
273 					state = state::error;
274 				}
275 				break;
276 			case state::close_seen:
277 				if (c == Tchar('{')) {
278 					state = state::error;
279 				} else if (c == Tchar('}')) {
280 					state = state::text;
281 					// output } here
282 				} else {
283 					state = state::error;
284 				}
285 				break;
286 			case state::error:
287 				state = state::error;
288 				break;
289 		}
290 	}
291 	if (state == state::error) {
292 		success = false;
293 	}
294 	if (state != state::text) {
295 		success = false;
296 	}
297 	if (numbered_args && unnumbered_args) {
298 		success = false;
299 	}
300 	if (!success) {
301 		throw format_message_syntax_error();
302 	}
303 	if (max_arg != args) {
304 		throw format_message_syntax_error();
305 	}
306 	return max_arg;
307 }
308 
309 
310 template <typename Tchar, std::size_t literal_length>
311 MPT_CONSTEXPRINLINE std::ptrdiff_t parse_format_string_argument_count(const Tchar (&format)[literal_length]) {
312 	return parse_format_string_argument_count_impl(format, literal_length - 1);
313 }
314 
315 
316 template <typename Tformatter, std::size_t args, typename Tchar, std::size_t N>
317 inline auto format_message(const Tchar (&format)[N]) {
318 	using Tstring = typename mpt::make_string_type<const Tchar *>::type;
319 	return message_formatter_counted<Tformatter, args, Tchar, Tstring>(format);
320 }
321 
322 template <typename Tformatter, std::size_t args, typename Tstring, typename Tchar, std::size_t N>
323 inline auto format_message_typed(const Tchar (&format)[N]) {
324 	return message_formatter_counted<Tformatter, args, Tchar, Tstring>(format);
325 }
326 
327 
328 
329 } // namespace MPT_INLINE_NS
330 } // namespace mpt
331 
332 
333 
334 #endif // MPT_FORMAT_MESSAGE_HPP
335