format.hpp (27929B)
1 #ifndef _C4_FORMAT_HPP_ 2 #define _C4_FORMAT_HPP_ 3 4 /** @file format.hpp provides type-safe facilities for formatting arguments 5 * to string buffers */ 6 7 #include "c4/charconv.hpp" 8 #include "c4/blob.hpp" 9 10 11 #ifdef _MSC_VER 12 # pragma warning(push) 13 # if C4_MSVC_VERSION != C4_MSVC_VERSION_2017 14 # pragma warning(disable: 4800) // forcing value to bool 'true' or 'false' (performance warning) 15 # endif 16 # pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe 17 #elif defined(__clang__) 18 # pragma clang diagnostic push 19 #elif defined(__GNUC__) 20 # pragma GCC diagnostic push 21 # pragma GCC diagnostic ignored "-Wuseless-cast" 22 #endif 23 24 namespace c4 { 25 26 27 //----------------------------------------------------------------------------- 28 //----------------------------------------------------------------------------- 29 //----------------------------------------------------------------------------- 30 // formatting truthy types as booleans 31 32 namespace fmt { 33 34 /** write a variable as an alphabetic boolean, ie as either true or false 35 * @param strict_read */ 36 template<class T> 37 struct boolalpha_ 38 { 39 boolalpha_(T val_, bool strict_read_=false) : val(val_ ? true : false), strict_read(strict_read_) {} 40 bool val; 41 bool strict_read; 42 }; 43 44 template<class T> 45 boolalpha_<T> boolalpha(T const& val, bool strict_read=false) 46 { 47 return boolalpha_<T>(val, strict_read); 48 } 49 50 } // namespace fmt 51 52 /** write a variable as an alphabetic boolean, ie as either true or false */ 53 template<class T> 54 inline size_t to_chars(substr buf, fmt::boolalpha_<T> fmt) 55 { 56 return to_chars(buf, fmt.val ? "true" : "false"); 57 } 58 59 60 61 //----------------------------------------------------------------------------- 62 //----------------------------------------------------------------------------- 63 //----------------------------------------------------------------------------- 64 // formatting integral types 65 66 namespace fmt { 67 68 /** format an integral type with a custom radix */ 69 template<typename T> 70 struct integral_ 71 { 72 T val; 73 T radix; 74 C4_ALWAYS_INLINE integral_(T val_, T radix_) : val(val_), radix(radix_) {} 75 }; 76 77 /** format an integral type with a custom radix, and pad with zeroes on the left */ 78 template<typename T> 79 struct integral_padded_ 80 { 81 T val; 82 T radix; 83 size_t num_digits; 84 C4_ALWAYS_INLINE integral_padded_(T val_, T radix_, size_t nd) : val(val_), radix(radix_), num_digits(nd) {} 85 }; 86 87 /** format an integral type with a custom radix */ 88 template<class T> 89 C4_ALWAYS_INLINE integral_<T> integral(T val, T radix=10) 90 { 91 return integral_<T>(val, radix); 92 } 93 /** format an integral type with a custom radix */ 94 template<class T> 95 C4_ALWAYS_INLINE integral_<intptr_t> integral(T const* val, T radix=10) 96 { 97 return integral_<intptr_t>(reinterpret_cast<intptr_t>(val), static_cast<intptr_t>(radix)); 98 } 99 /** format an integral type with a custom radix */ 100 template<class T> 101 C4_ALWAYS_INLINE integral_<intptr_t> integral(std::nullptr_t, T radix=10) 102 { 103 return integral_<intptr_t>(intptr_t(0), static_cast<intptr_t>(radix)); 104 } 105 /** pad the argument with zeroes on the left, with decimal radix */ 106 template<class T> 107 C4_ALWAYS_INLINE integral_padded_<T> zpad(T val, size_t num_digits) 108 { 109 return integral_padded_<T>(val, T(10), num_digits); 110 } 111 /** pad the argument with zeroes on the left */ 112 template<class T> 113 C4_ALWAYS_INLINE integral_padded_<T> zpad(integral_<T> val, size_t num_digits) 114 { 115 return integral_padded_<T>(val.val, val.radix, num_digits); 116 } 117 /** pad the argument with zeroes on the left */ 118 C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(std::nullptr_t, size_t num_digits) 119 { 120 return integral_padded_<intptr_t>(0, 16, num_digits); 121 } 122 /** pad the argument with zeroes on the left */ 123 template<class T> 124 C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(T const* val, size_t num_digits) 125 { 126 return integral_padded_<intptr_t>(reinterpret_cast<intptr_t>(val), 16, num_digits); 127 } 128 template<class T> 129 C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(T * val, size_t num_digits) 130 { 131 return integral_padded_<intptr_t>(reinterpret_cast<intptr_t>(val), 16, num_digits); 132 } 133 134 135 /** format the pointer as an hexadecimal value */ 136 template<class T> 137 inline integral_<intptr_t> hex(T * v) 138 { 139 return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(16)); 140 } 141 /** format the pointer as an hexadecimal value */ 142 template<class T> 143 inline integral_<intptr_t> hex(T const* v) 144 { 145 return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(16)); 146 } 147 /** format null as an hexadecimal value 148 * @overload hex */ 149 inline integral_<intptr_t> hex(std::nullptr_t) 150 { 151 return integral_<intptr_t>(0, intptr_t(16)); 152 } 153 /** format the integral_ argument as an hexadecimal value 154 * @overload hex */ 155 template<class T> 156 inline integral_<T> hex(T v) 157 { 158 return integral_<T>(v, T(16)); 159 } 160 161 /** format the pointer as an octal value */ 162 template<class T> 163 inline integral_<intptr_t> oct(T const* v) 164 { 165 return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(8)); 166 } 167 /** format the pointer as an octal value */ 168 template<class T> 169 inline integral_<intptr_t> oct(T * v) 170 { 171 return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(8)); 172 } 173 /** format null as an octal value */ 174 inline integral_<intptr_t> oct(std::nullptr_t) 175 { 176 return integral_<intptr_t>(intptr_t(0), intptr_t(8)); 177 } 178 /** format the integral_ argument as an octal value */ 179 template<class T> 180 inline integral_<T> oct(T v) 181 { 182 return integral_<T>(v, T(8)); 183 } 184 185 /** format the pointer as a binary 0-1 value 186 * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */ 187 template<class T> 188 inline integral_<intptr_t> bin(T const* v) 189 { 190 return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(2)); 191 } 192 /** format the pointer as a binary 0-1 value 193 * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */ 194 template<class T> 195 inline integral_<intptr_t> bin(T * v) 196 { 197 return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(2)); 198 } 199 /** format null as a binary 0-1 value 200 * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */ 201 inline integral_<intptr_t> bin(std::nullptr_t) 202 { 203 return integral_<intptr_t>(intptr_t(0), intptr_t(2)); 204 } 205 /** format the integral_ argument as a binary 0-1 value 206 * @see c4::raw() if you want to use a raw memcpy-based binary dump instead of 0-1 formatting */ 207 template<class T> 208 inline integral_<T> bin(T v) 209 { 210 return integral_<T>(v, T(2)); 211 } 212 213 214 template<class T> 215 struct overflow_checked_ 216 { 217 static_assert(std::is_integral<T>::value, "range checking only for integral types"); 218 C4_ALWAYS_INLINE overflow_checked_(T &val_) : val(&val_) {} 219 T *val; 220 }; 221 template<class T> 222 C4_ALWAYS_INLINE overflow_checked_<T> overflow_checked(T &val) 223 { 224 return overflow_checked_<T>(val); 225 } 226 227 } // namespace fmt 228 229 /** format an integral_ signed type */ 230 template<typename T> 231 C4_ALWAYS_INLINE 232 typename std::enable_if<std::is_signed<T>::value, size_t>::type 233 to_chars(substr buf, fmt::integral_<T> fmt) 234 { 235 return itoa(buf, fmt.val, fmt.radix); 236 } 237 /** format an integral_ signed type, pad with zeroes */ 238 template<typename T> 239 C4_ALWAYS_INLINE 240 typename std::enable_if<std::is_signed<T>::value, size_t>::type 241 to_chars(substr buf, fmt::integral_padded_<T> fmt) 242 { 243 return itoa(buf, fmt.val, fmt.radix, fmt.num_digits); 244 } 245 246 /** format an integral_ unsigned type */ 247 template<typename T> 248 C4_ALWAYS_INLINE 249 typename std::enable_if<std::is_unsigned<T>::value, size_t>::type 250 to_chars(substr buf, fmt::integral_<T> fmt) 251 { 252 return utoa(buf, fmt.val, fmt.radix); 253 } 254 /** format an integral_ unsigned type, pad with zeroes */ 255 template<typename T> 256 C4_ALWAYS_INLINE 257 typename std::enable_if<std::is_unsigned<T>::value, size_t>::type 258 to_chars(substr buf, fmt::integral_padded_<T> fmt) 259 { 260 return utoa(buf, fmt.val, fmt.radix, fmt.num_digits); 261 } 262 263 template<class T> 264 C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_<T> wrapper) 265 { 266 if(C4_LIKELY(!overflows<T>(s))) 267 return atox(s, wrapper.val); 268 return false; 269 } 270 271 272 //----------------------------------------------------------------------------- 273 //----------------------------------------------------------------------------- 274 //----------------------------------------------------------------------------- 275 // formatting real types 276 277 namespace fmt { 278 279 template<class T> 280 struct real_ 281 { 282 T val; 283 int precision; 284 RealFormat_e fmt; 285 real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT) : val(v), precision(prec), fmt(f) {} 286 }; 287 288 template<class T> 289 real_<T> real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT) 290 { 291 return real_<T>(val, precision, fmt); 292 } 293 294 } // namespace fmt 295 296 inline size_t to_chars(substr buf, fmt::real_< float> fmt) { return ftoa(buf, fmt.val, fmt.precision, fmt.fmt); } 297 inline size_t to_chars(substr buf, fmt::real_<double> fmt) { return dtoa(buf, fmt.val, fmt.precision, fmt.fmt); } 298 299 300 //----------------------------------------------------------------------------- 301 //----------------------------------------------------------------------------- 302 //----------------------------------------------------------------------------- 303 // writing raw binary data 304 305 namespace fmt { 306 307 /** @see blob_ */ 308 template<class T> 309 struct raw_wrapper_ : public blob_<T> 310 { 311 size_t alignment; 312 313 C4_ALWAYS_INLINE raw_wrapper_(blob_<T> data, size_t alignment_) noexcept 314 : 315 blob_<T>(data), 316 alignment(alignment_) 317 { 318 C4_ASSERT_MSG(alignment > 0 && (alignment & (alignment - 1)) == 0, "alignment must be a power of two"); 319 } 320 }; 321 322 using const_raw_wrapper = raw_wrapper_<cbyte>; 323 using raw_wrapper = raw_wrapper_<byte>; 324 325 /** mark a variable to be written in raw binary format, using memcpy 326 * @see blob_ */ 327 inline const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t)) 328 { 329 return const_raw_wrapper(data, alignment); 330 } 331 /** mark a variable to be written in raw binary format, using memcpy 332 * @see blob_ */ 333 inline const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t)) 334 { 335 return const_raw_wrapper(data, alignment); 336 } 337 /** mark a variable to be written in raw binary format, using memcpy 338 * @see blob_ */ 339 template<class T> 340 inline const_raw_wrapper craw(T const& C4_RESTRICT data, size_t alignment=alignof(T)) 341 { 342 return const_raw_wrapper(cblob(data), alignment); 343 } 344 /** mark a variable to be written in raw binary format, using memcpy 345 * @see blob_ */ 346 template<class T> 347 inline const_raw_wrapper raw(T const& C4_RESTRICT data, size_t alignment=alignof(T)) 348 { 349 return const_raw_wrapper(cblob(data), alignment); 350 } 351 352 /** mark a variable to be read in raw binary format, using memcpy */ 353 inline raw_wrapper raw(blob data, size_t alignment=alignof(max_align_t)) 354 { 355 return raw_wrapper(data, alignment); 356 } 357 /** mark a variable to be read in raw binary format, using memcpy */ 358 template<class T> 359 inline raw_wrapper raw(T & C4_RESTRICT data, size_t alignment=alignof(T)) 360 { 361 return raw_wrapper(blob(data), alignment); 362 } 363 364 } // namespace fmt 365 366 367 /** write a variable in raw binary format, using memcpy */ 368 C4CORE_EXPORT size_t to_chars(substr buf, fmt::const_raw_wrapper r); 369 370 /** read a variable in raw binary format, using memcpy */ 371 C4CORE_EXPORT bool from_chars(csubstr buf, fmt::raw_wrapper *r); 372 /** read a variable in raw binary format, using memcpy */ 373 inline bool from_chars(csubstr buf, fmt::raw_wrapper r) 374 { 375 return from_chars(buf, &r); 376 } 377 378 /** read a variable in raw binary format, using memcpy */ 379 inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper *r) 380 { 381 return from_chars(buf, r); 382 } 383 /** read a variable in raw binary format, using memcpy */ 384 inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper r) 385 { 386 return from_chars(buf, &r); 387 } 388 389 390 //----------------------------------------------------------------------------- 391 //----------------------------------------------------------------------------- 392 //----------------------------------------------------------------------------- 393 // formatting aligned to left/right 394 395 namespace fmt { 396 397 template<class T> 398 struct left_ 399 { 400 T val; 401 size_t width; 402 char pad; 403 left_(T v, size_t w, char p) : val(v), width(w), pad(p) {} 404 }; 405 406 template<class T> 407 struct right_ 408 { 409 T val; 410 size_t width; 411 char pad; 412 right_(T v, size_t w, char p) : val(v), width(w), pad(p) {} 413 }; 414 415 /** mark an argument to be aligned left */ 416 template<class T> 417 left_<T> left(T val, size_t width, char padchar=' ') 418 { 419 return left_<T>(val, width, padchar); 420 } 421 422 /** mark an argument to be aligned right */ 423 template<class T> 424 right_<T> right(T val, size_t width, char padchar=' ') 425 { 426 return right_<T>(val, width, padchar); 427 } 428 429 } // namespace fmt 430 431 432 template<class T> 433 size_t to_chars(substr buf, fmt::left_<T> const& C4_RESTRICT align) 434 { 435 size_t ret = to_chars(buf, align.val); 436 if(ret >= buf.len || ret >= align.width) 437 return ret > align.width ? ret : align.width; 438 buf.first(align.width).sub(ret).fill(align.pad); 439 to_chars(buf, align.val); 440 return align.width; 441 } 442 443 template<class T> 444 size_t to_chars(substr buf, fmt::right_<T> const& C4_RESTRICT align) 445 { 446 size_t ret = to_chars(buf, align.val); 447 if(ret >= buf.len || ret >= align.width) 448 return ret > align.width ? ret : align.width; 449 size_t rem = static_cast<size_t>(align.width - ret); 450 buf.first(rem).fill(align.pad); 451 to_chars(buf.sub(rem), align.val); 452 return align.width; 453 } 454 455 456 //----------------------------------------------------------------------------- 457 //----------------------------------------------------------------------------- 458 //----------------------------------------------------------------------------- 459 460 /// @cond dev 461 // terminates the variadic recursion 462 inline size_t cat(substr /*buf*/) 463 { 464 return 0; 465 } 466 /// @endcond 467 468 469 /** serialize the arguments, concatenating them to the given fixed-size buffer. 470 * The buffer size is strictly respected: no writes will occur beyond its end. 471 * @return the number of characters needed to write all the arguments into the buffer. 472 * @see c4::catrs() if instead of a fixed-size buffer, a resizeable container is desired 473 * @see c4::uncat() for the inverse function 474 * @see c4::catsep() if a separator between each argument is to be used 475 * @see c4::format() if a format string is desired */ 476 template<class Arg, class... Args> 477 size_t cat(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) 478 { 479 size_t num = to_chars(buf, a); 480 buf = buf.len >= num ? buf.sub(num) : substr{}; 481 num += cat(buf, more...); 482 return num; 483 } 484 485 /** like c4::cat() but return a substr instead of a size */ 486 template<class... Args> 487 substr cat_sub(substr buf, Args && ...args) 488 { 489 size_t sz = cat(buf, std::forward<Args>(args)...); 490 C4_CHECK(sz <= buf.len); 491 return {buf.str, sz <= buf.len ? sz : buf.len}; 492 } 493 494 495 //----------------------------------------------------------------------------- 496 497 /// @cond dev 498 // terminates the variadic recursion 499 inline size_t uncat(csubstr /*buf*/) 500 { 501 return 0; 502 } 503 /// @endcond 504 505 506 /** deserialize the arguments from the given buffer. 507 * 508 * @return the number of characters read from the buffer, or csubstr::npos 509 * if a conversion was not successful. 510 * @see c4::cat(). c4::uncat() is the inverse of c4::cat(). */ 511 template<class Arg, class... Args> 512 size_t uncat(csubstr buf, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) 513 { 514 size_t out = from_chars_first(buf, &a); 515 if(C4_UNLIKELY(out == csubstr::npos)) 516 return csubstr::npos; 517 buf = buf.len >= out ? buf.sub(out) : substr{}; 518 size_t num = uncat(buf, more...); 519 if(C4_UNLIKELY(num == csubstr::npos)) 520 return csubstr::npos; 521 return out + num; 522 } 523 524 525 526 //----------------------------------------------------------------------------- 527 //----------------------------------------------------------------------------- 528 //----------------------------------------------------------------------------- 529 530 namespace detail { 531 532 template<class Sep> 533 C4_ALWAYS_INLINE size_t catsep_more(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/) 534 { 535 return 0; 536 } 537 538 template<class Sep, class Arg, class... Args> 539 size_t catsep_more(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) 540 { 541 size_t ret = to_chars(buf, sep); 542 size_t num = ret; 543 buf = buf.len >= ret ? buf.sub(ret) : substr{}; 544 ret = to_chars(buf, a); 545 num += ret; 546 buf = buf.len >= ret ? buf.sub(ret) : substr{}; 547 ret = catsep_more(buf, sep, more...); 548 num += ret; 549 return num; 550 } 551 552 553 template<class Sep> 554 inline size_t uncatsep_more(csubstr /*buf*/, Sep & /*sep*/) 555 { 556 return 0; 557 } 558 559 template<class Sep, class Arg, class... Args> 560 size_t uncatsep_more(csubstr buf, Sep & C4_RESTRICT sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) 561 { 562 size_t ret = from_chars_first(buf, &sep); 563 size_t num = ret; 564 if(C4_UNLIKELY(ret == csubstr::npos)) 565 return csubstr::npos; 566 buf = buf.len >= ret ? buf.sub(ret) : substr{}; 567 ret = from_chars_first(buf, &a); 568 if(C4_UNLIKELY(ret == csubstr::npos)) 569 return csubstr::npos; 570 num += ret; 571 buf = buf.len >= ret ? buf.sub(ret) : substr{}; 572 ret = uncatsep_more(buf, sep, more...); 573 if(C4_UNLIKELY(ret == csubstr::npos)) 574 return csubstr::npos; 575 num += ret; 576 return num; 577 } 578 579 } // namespace detail 580 581 /// @cond dev 582 template<class Sep> 583 size_t catsep(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/) 584 { 585 return 0; 586 } 587 /// @endcond 588 589 /** serialize the arguments, concatenating them to the given fixed-size 590 * buffer, using a separator between each argument. 591 * The buffer size is strictly respected: no writes will occur beyond its end. 592 * @return the number of characters needed to write all the arguments into the buffer. 593 * @see c4::catseprs() if instead of a fixed-size buffer, a resizeable container is desired 594 * @see c4::uncatsep() for the inverse function (ie, reading instead of writing) 595 * @see c4::cat() if no separator is needed 596 * @see c4::format() if a format string is desired */ 597 template<class Sep, class Arg, class... Args> 598 size_t catsep(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) 599 { 600 size_t num = to_chars(buf, a); 601 buf = buf.len >= num ? buf.sub(num) : substr{}; 602 num += detail::catsep_more(buf, sep, more...); 603 return num; 604 } 605 606 /** like c4::catsep() but return a substr instead of a size 607 * @see c4::catsep(). c4::uncatsep() is the inverse of c4::catsep(). */ 608 template<class... Args> 609 substr catsep_sub(substr buf, Args && ...args) 610 { 611 size_t sz = catsep(buf, std::forward<Args>(args)...); 612 C4_CHECK(sz <= buf.len); 613 return {buf.str, sz <= buf.len ? sz : buf.len}; 614 } 615 616 /** deserialize the arguments from the given buffer, using a separator. 617 * 618 * @return the number of characters read from the buffer, or csubstr::npos 619 * if a conversion was not successful 620 * @see c4::catsep(). c4::uncatsep() is the inverse of c4::catsep(). */ 621 template<class Sep, class Arg, class... Args> 622 size_t uncatsep(csubstr buf, Sep & C4_RESTRICT sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) 623 { 624 size_t ret = from_chars_first(buf, &a), num = ret; 625 if(C4_UNLIKELY(ret == csubstr::npos)) 626 return csubstr::npos; 627 buf = buf.len >= ret ? buf.sub(ret) : substr{}; 628 ret = detail::uncatsep_more(buf, sep, more...); 629 if(C4_UNLIKELY(ret == csubstr::npos)) 630 return csubstr::npos; 631 num += ret; 632 return num; 633 } 634 635 636 //----------------------------------------------------------------------------- 637 //----------------------------------------------------------------------------- 638 //----------------------------------------------------------------------------- 639 640 /// @cond dev 641 // terminates the variadic recursion 642 inline size_t format(substr buf, csubstr fmt) 643 { 644 return to_chars(buf, fmt); 645 } 646 /// @endcond 647 648 649 /** using a format string, serialize the arguments into the given 650 * fixed-size buffer. 651 * The buffer size is strictly respected: no writes will occur beyond its end. 652 * In the format string, each argument is marked with a compact 653 * curly-bracket pair: {}. Arguments beyond the last curly bracket pair 654 * are silently ignored. For example: 655 * @code{.cpp} 656 * c4::format(buf, "the {} drank {} {}", "partier", 5, "beers"); // the partier drank 5 beers 657 * c4::format(buf, "the {} drank {} {}", "programmer", 6, "coffees"); // the programmer drank 6 coffees 658 * @endcode 659 * @return the number of characters needed to write into the buffer. 660 * @see c4::formatrs() if instead of a fixed-size buffer, a resizeable container is desired 661 * @see c4::unformat() for the inverse function 662 * @see c4::cat() if no format or separator is needed 663 * @see c4::catsep() if no format is needed, but a separator must be used */ 664 template<class Arg, class... Args> 665 size_t format(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more) 666 { 667 size_t pos = fmt.find("{}"); // @todo use _find_fmt() 668 if(C4_UNLIKELY(pos == csubstr::npos)) 669 return to_chars(buf, fmt); 670 size_t num = to_chars(buf, fmt.sub(0, pos)); 671 size_t out = num; 672 buf = buf.len >= num ? buf.sub(num) : substr{}; 673 num = to_chars(buf, a); 674 out += num; 675 buf = buf.len >= num ? buf.sub(num) : substr{}; 676 num = format(buf, fmt.sub(pos + 2), more...); 677 out += num; 678 return out; 679 } 680 681 /** like c4::format() but return a substr instead of a size 682 * @see c4::format() 683 * @see c4::catsep(). uncatsep() is the inverse of catsep(). */ 684 template<class... Args> 685 substr format_sub(substr buf, csubstr fmt, Args const& C4_RESTRICT ...args) 686 { 687 size_t sz = c4::format(buf, fmt, args...); 688 C4_CHECK(sz <= buf.len); 689 return {buf.str, sz <= buf.len ? sz : buf.len}; 690 } 691 692 693 //----------------------------------------------------------------------------- 694 695 /// @cond dev 696 // terminates the variadic recursion 697 inline size_t unformat(csubstr /*buf*/, csubstr fmt) 698 { 699 return fmt.len; 700 } 701 /// @endcond 702 703 704 /** using a format string, deserialize the arguments from the given 705 * buffer. 706 * @return the number of characters read from the buffer, or npos if a conversion failed. 707 * @see c4::format(). c4::unformat() is the inverse function to format(). */ 708 template<class Arg, class... Args> 709 size_t unformat(csubstr buf, csubstr fmt, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more) 710 { 711 const size_t pos = fmt.find("{}"); 712 if(C4_UNLIKELY(pos == csubstr::npos)) 713 return unformat(buf, fmt); 714 size_t num = pos; 715 size_t out = num; 716 buf = buf.len >= num ? buf.sub(num) : substr{}; 717 num = from_chars_first(buf, &a); 718 if(C4_UNLIKELY(num == csubstr::npos)) 719 return csubstr::npos; 720 out += num; 721 buf = buf.len >= num ? buf.sub(num) : substr{}; 722 num = unformat(buf, fmt.sub(pos + 2), more...); 723 if(C4_UNLIKELY(num == csubstr::npos)) 724 return csubstr::npos; 725 out += num; 726 return out; 727 } 728 729 730 //----------------------------------------------------------------------------- 731 //----------------------------------------------------------------------------- 732 //----------------------------------------------------------------------------- 733 734 /** like c4::cat(), but receives a container, and resizes it as needed to contain 735 * the result. The container is overwritten. To append to it, use the append 736 * overload. 737 * @see c4::cat() */ 738 template<class CharOwningContainer, class... Args> 739 inline void catrs(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args) 740 { 741 retry: 742 substr buf = to_substr(*cont); 743 size_t ret = cat(buf, args...); 744 cont->resize(ret); 745 if(ret > buf.len) 746 goto retry; 747 } 748 749 /** like c4::cat(), but creates and returns a new container sized as needed to contain 750 * the result. 751 * @see c4::cat() */ 752 template<class CharOwningContainer, class... Args> 753 inline CharOwningContainer catrs(Args const& C4_RESTRICT ...args) 754 { 755 CharOwningContainer cont; 756 catrs(&cont, args...); 757 return cont; 758 } 759 760 /** like c4::cat(), but receives a container, and appends to it instead of 761 * overwriting it. The container is resized as needed to contain the result. 762 * @return the region newly appended to the original container 763 * @see c4::cat() 764 * @see c4::catrs() */ 765 template<class CharOwningContainer, class... Args> 766 inline csubstr catrs_append(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args) 767 { 768 const size_t pos = cont->size(); 769 retry: 770 substr buf = to_substr(*cont).sub(pos); 771 size_t ret = cat(buf, args...); 772 cont->resize(pos + ret); 773 if(ret > buf.len) 774 goto retry; 775 return to_csubstr(*cont).range(pos, cont->size()); 776 } 777 778 779 //----------------------------------------------------------------------------- 780 781 /** like c4::catsep(), but receives a container, and resizes it as needed to contain the result. 782 * The container is overwritten. To append to the container use the append overload. 783 * @see c4::catsep() */ 784 template<class CharOwningContainer, class Sep, class... Args> 785 inline void catseprs(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args) 786 { 787 retry: 788 substr buf = to_substr(*cont); 789 size_t ret = catsep(buf, sep, args...); 790 cont->resize(ret); 791 if(ret > buf.len) 792 goto retry; 793 } 794 795 /** like c4::catsep(), but create a new container with the result. 796 * @return the requested container */ 797 template<class CharOwningContainer, class Sep, class... Args> 798 inline CharOwningContainer catseprs(Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args) 799 { 800 CharOwningContainer cont; 801 catseprs(&cont, sep, args...); 802 return cont; 803 } 804 805 806 /** like catsep(), but receives a container, and appends the arguments, resizing the 807 * container as needed to contain the result. The buffer is appended to. 808 * @return a csubstr of the appended part 809 * @ingroup formatting_functions */ 810 template<class CharOwningContainer, class Sep, class... Args> 811 inline csubstr catseprs_append(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args) 812 { 813 const size_t pos = cont->size(); 814 retry: 815 substr buf = to_substr(*cont).sub(pos); 816 size_t ret = catsep(buf, sep, args...); 817 cont->resize(pos + ret); 818 if(ret > buf.len) 819 goto retry; 820 return to_csubstr(*cont).range(pos, cont->size()); 821 } 822 823 824 //----------------------------------------------------------------------------- 825 826 /** like c4::format(), but receives a container, and resizes it as needed 827 * to contain the result. The container is overwritten. To append to 828 * the container use the append overload. 829 * @see c4::format() */ 830 template<class CharOwningContainer, class... Args> 831 inline void formatrs(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args) 832 { 833 retry: 834 substr buf = to_substr(*cont); 835 size_t ret = format(buf, fmt, args...); 836 cont->resize(ret); 837 if(ret > buf.len) 838 goto retry; 839 } 840 841 /** like c4::format(), but create a new container with the result. 842 * @return the requested container */ 843 template<class CharOwningContainer, class... Args> 844 inline CharOwningContainer formatrs(csubstr fmt, Args const& C4_RESTRICT ...args) 845 { 846 CharOwningContainer cont; 847 formatrs(&cont, fmt, args...); 848 return cont; 849 } 850 851 /** like format(), but receives a container, and appends the 852 * arguments, resizing the container as needed to contain the 853 * result. The buffer is appended to. 854 * @return the region newly appended to the original container 855 * @ingroup formatting_functions */ 856 template<class CharOwningContainer, class... Args> 857 inline csubstr formatrs_append(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args) 858 { 859 const size_t pos = cont->size(); 860 retry: 861 substr buf = to_substr(*cont).sub(pos); 862 size_t ret = format(buf, fmt, args...); 863 cont->resize(pos + ret); 864 if(ret > buf.len) 865 goto retry; 866 return to_csubstr(*cont).range(pos, cont->size()); 867 } 868 869 } // namespace c4 870 871 #ifdef _MSC_VER 872 # pragma warning(pop) 873 #elif defined(__clang__) 874 # pragma clang diagnostic pop 875 #elif defined(__GNUC__) 876 # pragma GCC diagnostic pop 877 #endif 878 879 #endif /* _C4_FORMAT_HPP_ */