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