rapidyaml  0.7.0
parse and emit YAML, and do it fast
format.hpp
Go to the documentation of this file.
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 /** @defgroup doc_format_utils Format utilities
25  *
26  * @brief Provides generic and type-safe formatting/scanning utilities
27  * built on top of @ref doc_to_chars() and @ref doc_from_chars,
28  * forwarding the arguments to these functions, which in turn use the
29  * @ref doc_charconv utilities. Like @ref doc_charconv, the formatting
30  * facilities are very efficient and many times faster than printf().
31  *
32  * @see [a formatting sample in rapidyaml's docs](https://rapidyaml.readthedocs.io/latest/doxygen/group__doc__quickstart.html#gac2425b515eb552589708cfff70c52b14)
33  * */
34 
35 /** @defgroup doc_format_specifiers Format specifiers
36  *
37  * @brief Format specifiers are tag types and functions that are used
38  * together with @ref doc_to_chars and @ref doc_from_chars
39  *
40  * @see [a formatting sample in rapidyaml's docs](https://rapidyaml.readthedocs.io/latest/doxygen/group__doc__quickstart.html#gac2425b515eb552589708cfff70c52b14)
41  * @ingroup doc_format_utils */
42 
43 namespace c4 {
44 
45 /** @addtogroup doc_format_utils
46  * @{ */
47 
48 //-----------------------------------------------------------------------------
49 //-----------------------------------------------------------------------------
50 //-----------------------------------------------------------------------------
51 // formatting truthy types as booleans
52 
53 namespace fmt {
54 
55 /** @addtogroup doc_format_specifiers
56  * @{ */
57 
58 /** @defgroup doc_boolean_specifiers boolean specifiers
59  * @{ */
60 
61 /** write a variable as an alphabetic boolean, ie as either true or false
62  * @param strict_read */
63 template<class T>
64 struct boolalpha_
65 {
66  boolalpha_(T val_, bool strict_read_=false) : val(val_ ? true : false), strict_read(strict_read_) {}
67  bool val;
69 };
70 
71 template<class T>
72 boolalpha_<T> boolalpha(T const& val, bool strict_read=false)
73 {
74  return boolalpha_<T>(val, strict_read);
75 }
76 
77 /** @} */
78 
79 /** @} */
80 
81 } // namespace fmt
82 
83 /** write a variable as an alphabetic boolean, ie as either true or
84  * false
85  * @ingroup doc_to_chars */
86 template<class T>
87 inline size_t to_chars(substr buf, fmt::boolalpha_<T> fmt)
88 {
89  return to_chars(buf, fmt.val ? "true" : "false");
90 }
91 
92 
93 
94 //-----------------------------------------------------------------------------
95 //-----------------------------------------------------------------------------
96 //-----------------------------------------------------------------------------
97 // formatting integral types
98 
99 namespace fmt {
100 
101 /** @addtogroup doc_format_specifiers
102  * @{ */
103 
104 /** @defgroup doc_integer_specifiers Integer specifiers
105  * @{ */
106 
107 /** format an integral type with a custom radix */
108 template<typename T>
109 struct integral_
110 {
111  C4_STATIC_ASSERT(std::is_integral<T>::value);
112  T val;
113  T radix;
114  C4_ALWAYS_INLINE integral_(T val_, T radix_) : val(val_), radix(radix_) {}
115 };
116 
117 /** format an integral type with a custom radix, and pad with zeroes on the left */
118 template<typename T>
120 {
121  C4_STATIC_ASSERT(std::is_integral<T>::value);
122  T val;
123  T radix;
124  size_t num_digits;
125  C4_ALWAYS_INLINE integral_padded_(T val_, T radix_, size_t nd) : val(val_), radix(radix_), num_digits(nd) {}
126 };
127 
128 
129 /** format an integral type with a custom radix */
130 template<class T>
131 C4_ALWAYS_INLINE integral_<T> integral(T val, T radix=10)
132 {
133  return integral_<T>(val, radix);
134 }
135 /** format an integral type with a custom radix */
136 template<class T>
137 C4_ALWAYS_INLINE integral_<intptr_t> integral(T const* val, T radix=10)
138 {
139  return integral_<intptr_t>(reinterpret_cast<intptr_t>(val), static_cast<intptr_t>(radix));
140 }
141 /** format an integral type with a custom radix */
142 template<class T>
143 C4_ALWAYS_INLINE integral_<intptr_t> integral(std::nullptr_t, T radix=10)
144 {
145  return integral_<intptr_t>(intptr_t(0), static_cast<intptr_t>(radix));
146 }
147 
148 
149 /** format the pointer as an hexadecimal value */
150 template<class T>
152 {
153  return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(16));
154 }
155 /** format the pointer as an hexadecimal value */
156 template<class T>
157 inline integral_<intptr_t> hex(T const* v)
158 {
159  return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(16));
160 }
161 /** format null as an hexadecimal value
162  * @overload hex */
163 inline integral_<intptr_t> hex(std::nullptr_t)
164 {
165  return integral_<intptr_t>(0, intptr_t(16));
166 }
167 /** format the integral_ argument as an hexadecimal value
168  * @overload hex */
169 template<class T>
170 inline integral_<T> hex(T v)
171 {
172  return integral_<T>(v, T(16));
173 }
174 
175 /** format the pointer as an octal value */
176 template<class T>
177 inline integral_<intptr_t> oct(T const* v)
178 {
179  return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(8));
180 }
181 /** format the pointer as an octal value */
182 template<class T>
184 {
185  return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(8));
186 }
187 /** format null as an octal value */
188 inline integral_<intptr_t> oct(std::nullptr_t)
189 {
190  return integral_<intptr_t>(intptr_t(0), intptr_t(8));
191 }
192 /** format the integral_ argument as an octal value */
193 template<class T>
194 inline integral_<T> oct(T v)
195 {
196  return integral_<T>(v, T(8));
197 }
198 
199 /** format the pointer as a binary 0-1 value
200  * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */
201 template<class T>
202 inline integral_<intptr_t> bin(T const* v)
203 {
204  return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(2));
205 }
206 /** format the pointer as a binary 0-1 value
207  * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */
208 template<class T>
210 {
211  return integral_<intptr_t>(reinterpret_cast<intptr_t>(v), intptr_t(2));
212 }
213 /** format null as a binary 0-1 value
214  * @see c4::raw() if you want to use a binary memcpy instead of 0-1 formatting */
215 inline integral_<intptr_t> bin(std::nullptr_t)
216 {
217  return integral_<intptr_t>(intptr_t(0), intptr_t(2));
218 }
219 /** format the integral_ argument as a binary 0-1 value
220  * @see c4::raw() if you want to use a raw memcpy-based binary dump instead of 0-1 formatting */
221 template<class T>
222 inline integral_<T> bin(T v)
223 {
224  return integral_<T>(v, T(2));
225 }
226 
227 /** @} */ // integer_specifiers
228 
229 
230 /** @defgroup doc_zpad Pad the number with zeroes on the left
231  * @{ */
232 
233 /** pad the argument with zeroes on the left, with decimal radix */
234 template<class T>
235 C4_ALWAYS_INLINE integral_padded_<T> zpad(T val, size_t num_digits)
236 {
237  return integral_padded_<T>(val, T(10), num_digits);
238 }
239 /** pad the argument with zeroes on the left */
240 template<class T>
241 C4_ALWAYS_INLINE integral_padded_<T> zpad(integral_<T> val, size_t num_digits)
242 {
243  return integral_padded_<T>(val.val, val.radix, num_digits);
244 }
245 /** pad the argument with zeroes on the left */
246 C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(std::nullptr_t, size_t num_digits)
247 {
248  return integral_padded_<intptr_t>(0, 16, num_digits);
249 }
250 /** pad the argument with zeroes on the left */
251 template<class T>
252 C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(T const* val, size_t num_digits)
253 {
254  return integral_padded_<intptr_t>(reinterpret_cast<intptr_t>(val), 16, num_digits);
255 }
256 template<class T>
257 C4_ALWAYS_INLINE integral_padded_<intptr_t> zpad(T * val, size_t num_digits)
258 {
259  return integral_padded_<intptr_t>(reinterpret_cast<intptr_t>(val), 16, num_digits);
260 }
261 
262 /** @} */ // zpad
263 
264 
265 /** @defgroup doc_overflow_checked Check read for overflow
266  * @{ */
267 
268 template<class T>
270 {
271  static_assert(std::is_integral<T>::value, "range checking only for integral types");
272  C4_ALWAYS_INLINE overflow_checked_(T &val_) : val(&val_) {}
273  T *val;
274 };
275 template<class T>
276 C4_ALWAYS_INLINE overflow_checked_<T> overflow_checked(T &val)
277 {
278  return overflow_checked_<T>(val);
279 }
280 
281 /** @} */ // overflow_checked
282 
283 /** @} */ // format_specifiers
284 
285 
286 } // namespace fmt
287 
288 /** format an integer signed type
289  * @ingroup doc_to_chars */
290 template<typename T>
291 C4_ALWAYS_INLINE
292 typename std::enable_if<std::is_signed<T>::value, size_t>::type
293 to_chars(substr buf, fmt::integral_<T> fmt)
294 {
295  return itoa(buf, fmt.val, fmt.radix);
296 }
297 /** format an integer signed type, pad with zeroes
298  * @ingroup doc_to_chars */
299 template<typename T>
300 C4_ALWAYS_INLINE
301 typename std::enable_if<std::is_signed<T>::value, size_t>::type
303 {
304  return itoa(buf, fmt.val, fmt.radix, fmt.num_digits);
305 }
306 
307 /** format an integer unsigned type
308  * @ingroup doc_to_chars */
309 template<typename T>
310 C4_ALWAYS_INLINE
311 typename std::enable_if<std::is_unsigned<T>::value, size_t>::type
312 to_chars(substr buf, fmt::integral_<T> fmt)
313 {
314  return utoa(buf, fmt.val, fmt.radix);
315 }
316 /** format an integer unsigned type, pad with zeroes
317  * @ingroup doc_to_chars */
318 template<typename T>
319 C4_ALWAYS_INLINE
320 typename std::enable_if<std::is_unsigned<T>::value, size_t>::type
322 {
323  return utoa(buf, fmt.val, fmt.radix, fmt.num_digits);
324 }
325 
326 /** read an format an integer unsigned type
327  * @ingroup doc_from_chars */
328 template<class T>
329 C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_<T> wrapper)
330 {
331  if(C4_LIKELY(!overflows<T>(s)))
332  return atox(s, wrapper.val);
333  return false;
334 }
335 
336 
337 //-----------------------------------------------------------------------------
338 //-----------------------------------------------------------------------------
339 //-----------------------------------------------------------------------------
340 // formatting real types
341 
342 namespace fmt {
343 
344 /** @addtogroup doc_format_specifiers
345  * @{ */
346 
347 /** @defgroup doc_real_specifiers Real specifiers
348  * @{ */
349 
350 template<class T>
351 struct real_
352 {
353  T val;
356  real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT) : val(v), precision(prec), fmt(f) {}
357 };
358 
359 template<class T>
360 real_<T> real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
361 {
362  return real_<T>(val, precision, fmt);
363 }
364 
365 /** @} */ // real_specifiers
366 
367 /** @} */ // format_specifiers
368 
369 } // namespace fmt
370 
371 /** @ingroup doc_to_chars */
372 inline size_t to_chars(substr buf, fmt::real_< float> fmt) { return ftoa(buf, fmt.val, fmt.precision, fmt.fmt); }
373 /** @ingroup doc_to_chars */
374 inline size_t to_chars(substr buf, fmt::real_<double> fmt) { return dtoa(buf, fmt.val, fmt.precision, fmt.fmt); }
375 
376 
377 //-----------------------------------------------------------------------------
378 //-----------------------------------------------------------------------------
379 //-----------------------------------------------------------------------------
380 // writing raw binary data
381 
382 namespace fmt {
383 
384 /** @addtogroup doc_format_specifiers
385  * @{ */
386 
387 /** @defgroup doc_raw_binary_specifiers Raw binary data
388  * @{ */
389 
390 /** @see blob_ */
391 template<class T>
392 struct raw_wrapper_ : public blob_<T>
393 {
394  size_t alignment;
395 
396  C4_ALWAYS_INLINE raw_wrapper_(blob_<T> data, size_t alignment_) noexcept
397  :
398  blob_<T>(data),
399  alignment(alignment_)
400  {
401  C4_ASSERT_MSG(alignment > 0 && (alignment & (alignment - 1)) == 0, "alignment must be a power of two");
402  }
403 };
404 
407 
408 /** mark a variable to be written in raw binary format, using memcpy
409  * @see blob_ */
410 inline const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t))
411 {
412  return const_raw_wrapper(data, alignment);
413 }
414 /** mark a variable to be written in raw binary format, using memcpy
415  * @see blob_ */
416 inline const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t))
417 {
418  return const_raw_wrapper(data, alignment);
419 }
420 /** mark a variable to be written in raw binary format, using memcpy
421  * @see blob_ */
422 template<class T>
423 inline const_raw_wrapper craw(T const& C4_RESTRICT data, size_t alignment=alignof(T))
424 {
425  return const_raw_wrapper(cblob(data), alignment);
426 }
427 /** mark a variable to be written in raw binary format, using memcpy
428  * @see blob_ */
429 template<class T>
430 inline const_raw_wrapper raw(T const& C4_RESTRICT data, size_t alignment=alignof(T))
431 {
432  return const_raw_wrapper(cblob(data), alignment);
433 }
434 
435 /** mark a variable to be read in raw binary format, using memcpy */
436 inline raw_wrapper raw(blob data, size_t alignment=alignof(max_align_t))
437 {
438  return raw_wrapper(data, alignment);
439 }
440 /** mark a variable to be read in raw binary format, using memcpy */
441 template<class T>
442 inline raw_wrapper raw(T & C4_RESTRICT data, size_t alignment=alignof(T))
443 {
444  return raw_wrapper(blob(data), alignment);
445 }
446 
447 /** @} */ // raw_binary_specifiers
448 
449 /** @} */ // format_specifiers
450 
451 } // namespace fmt
452 
453 
454 /** write a variable in raw binary format, using memcpy
455  * @ingroup doc_to_chars */
456 C4CORE_EXPORT size_t to_chars(substr buf, fmt::const_raw_wrapper r);
457 
458 /** read a variable in raw binary format, using memcpy
459  * @ingroup doc_from_chars */
460 C4CORE_EXPORT bool from_chars(csubstr buf, fmt::raw_wrapper *r);
461 /** read a variable in raw binary format, using memcpy
462  * @ingroup doc_from_chars */
463 inline bool from_chars(csubstr buf, fmt::raw_wrapper r)
464 {
465  return from_chars(buf, &r);
466 }
467 
468 /** read a variable in raw binary format, using memcpy
469  * @ingroup doc_from_chars_first */
470 inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper *r)
471 {
472  return from_chars(buf, r);
473 }
474 /** read a variable in raw binary format, using memcpy
475  * @ingroup doc_from_chars_first */
476 inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper r)
477 {
478  return from_chars(buf, &r);
479 }
480 
481 
482 //-----------------------------------------------------------------------------
483 //-----------------------------------------------------------------------------
484 //-----------------------------------------------------------------------------
485 // formatting aligned to left/right
486 
487 namespace fmt {
488 
489 /** @addtogroup doc_format_specifiers
490  * @{ */
491 
492 /** @defgroup doc_alignment_specifiers Alignment specifiers
493  * @{ */
494 
495 template<class T>
496 struct left_
497 {
498  T val;
499  size_t width;
500  char pad;
501  left_(T v, size_t w, char p) : val(v), width(w), pad(p) {}
502 };
503 
504 template<class T>
505 struct right_
506 {
507  T val;
508  size_t width;
509  char pad;
510  right_(T v, size_t w, char p) : val(v), width(w), pad(p) {}
511 };
512 
513 /** mark an argument to be aligned left */
514 template<class T>
515 left_<T> left(T val, size_t width, char padchar=' ')
516 {
517  return left_<T>(val, width, padchar);
518 }
519 
520 /** mark an argument to be aligned right */
521 template<class T>
522 right_<T> right(T val, size_t width, char padchar=' ')
523 {
524  return right_<T>(val, width, padchar);
525 }
526 
527 /** @} */ // alignment_specifiers
528 
529 /** @} */ // format_specifiers
530 
531 } // namespace fmt
532 
533 
534 /** @ingroup doc_to_chars */
535 template<class T>
536 size_t to_chars(substr buf, fmt::left_<T> const& C4_RESTRICT align)
537 {
538  size_t ret = to_chars(buf, align.val);
539  if(ret >= buf.len || ret >= align.width)
540  return ret > align.width ? ret : align.width;
541  buf.first(align.width).sub(ret).fill(align.pad);
542  to_chars(buf, align.val);
543  return align.width;
544 }
545 
546 /** @ingroup doc_to_chars */
547 template<class T>
548 size_t to_chars(substr buf, fmt::right_<T> const& C4_RESTRICT align)
549 {
550  size_t ret = to_chars(buf, align.val);
551  if(ret >= buf.len || ret >= align.width)
552  return ret > align.width ? ret : align.width;
553  size_t rem = static_cast<size_t>(align.width - ret);
554  buf.first(rem).fill(align.pad);
555  to_chars(buf.sub(rem), align.val);
556  return align.width;
557 }
558 
559 
560 //-----------------------------------------------------------------------------
561 //-----------------------------------------------------------------------------
562 //-----------------------------------------------------------------------------
563 
564 /** @defgroup doc_cat cat: concatenate arguments to string
565  * @{ */
566 
567 /** @cond dev */
568 // terminates the variadic recursion
569 inline size_t cat(substr /*buf*/)
570 {
571  return 0;
572 }
573 /** @endcond */
574 
575 
576 /** serialize the arguments, concatenating them to the given fixed-size buffer.
577  * The buffer size is strictly respected: no writes will occur beyond its end.
578  * @return the number of characters needed to write all the arguments into the buffer.
579  * @see c4::catrs() if instead of a fixed-size buffer, a resizeable container is desired
580  * @see c4::uncat() for the inverse function
581  * @see c4::catsep() if a separator between each argument is to be used
582  * @see c4::format() if a format string is desired */
583 template<class Arg, class... Args>
584 size_t cat(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
585 {
586  size_t num = to_chars(buf, a);
587  buf = buf.len >= num ? buf.sub(num) : substr{};
588  num += cat(buf, more...);
589  return num;
590 }
591 
592 /** like c4::cat() but return a substr instead of a size */
593 template<class... Args>
594 substr cat_sub(substr buf, Args && ...args)
595 {
596  size_t sz = cat(buf, std::forward<Args>(args)...);
597  C4_CHECK(sz <= buf.len);
598  return {buf.str, sz <= buf.len ? sz : buf.len};
599 }
600 
601 /** @} */
602 
603 
604 //-----------------------------------------------------------------------------
605 
606 
607 /** @defgroup doc_uncat uncat: read concatenated arguments from string
608  * @{ */
609 
610 /** @cond dev */
611 // terminates the variadic recursion
612 inline size_t uncat(csubstr /*buf*/)
613 {
614  return 0;
615 }
616 /** @endcond */
617 
618 
619 /** deserialize the arguments from the given buffer.
620  *
621  * @return the number of characters read from the buffer, or csubstr::npos
622  * if a conversion was not successful.
623  * @see c4::cat(). c4::uncat() is the inverse of c4::cat(). */
624 template<class Arg, class... Args>
625 size_t uncat(csubstr buf, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
626 {
627  size_t out = from_chars_first(buf, &a);
628  if(C4_UNLIKELY(out == csubstr::npos))
629  return csubstr::npos;
630  buf = buf.len >= out ? buf.sub(out) : substr{};
631  size_t num = uncat(buf, more...);
632  if(C4_UNLIKELY(num == csubstr::npos))
633  return csubstr::npos;
634  return out + num;
635 }
636 
637 /** @} */
638 
639 
640 
641 //-----------------------------------------------------------------------------
642 //-----------------------------------------------------------------------------
643 //-----------------------------------------------------------------------------
644 
645 
646 /** @defgroup doc_catsep catsep: cat arguments to string with separator
647  * @{ */
648 
649 /** @cond dev */
650 namespace detail {
651 template<class Sep>
652 C4_ALWAYS_INLINE size_t catsep_more(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/)
653 {
654  return 0;
655 }
656 
657 template<class Sep, class Arg, class... Args>
658 size_t catsep_more(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
659 {
660  size_t ret = to_chars(buf, sep);
661  size_t num = ret;
662  buf = buf.len >= ret ? buf.sub(ret) : substr{};
663  ret = to_chars(buf, a);
664  num += ret;
665  buf = buf.len >= ret ? buf.sub(ret) : substr{};
666  ret = catsep_more(buf, sep, more...);
667  num += ret;
668  return num;
669 }
670 
671 
672 template<class Sep>
673 inline size_t uncatsep_more(csubstr /*buf*/, Sep & /*sep*/)
674 {
675  return 0;
676 }
677 
678 template<class Sep, class Arg, class... Args>
679 size_t uncatsep_more(csubstr buf, Sep & C4_RESTRICT sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
680 {
681  size_t ret = from_chars_first(buf, &sep);
682  size_t num = ret;
683  if(C4_UNLIKELY(ret == csubstr::npos))
684  return csubstr::npos;
685  buf = buf.len >= ret ? buf.sub(ret) : substr{};
686  ret = from_chars_first(buf, &a);
687  if(C4_UNLIKELY(ret == csubstr::npos))
688  return csubstr::npos;
689  num += ret;
690  buf = buf.len >= ret ? buf.sub(ret) : substr{};
691  ret = uncatsep_more(buf, sep, more...);
692  if(C4_UNLIKELY(ret == csubstr::npos))
693  return csubstr::npos;
694  num += ret;
695  return num;
696 }
697 
698 } // namespace detail
699 
700 template<class Sep>
701 size_t catsep(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/)
702 {
703  return 0;
704 }
705 /** @endcond */
706 
707 
708 /** serialize the arguments, concatenating them to the given fixed-size
709  * buffer, using a separator between each argument.
710  * The buffer size is strictly respected: no writes will occur beyond its end.
711  * @return the number of characters needed to write all the arguments into the buffer.
712  * @see c4::catseprs() if instead of a fixed-size buffer, a resizeable container is desired
713  * @see c4::uncatsep() for the inverse function (ie, reading instead of writing)
714  * @see c4::cat() if no separator is needed
715  * @see c4::format() if a format string is desired */
716 template<class Sep, class Arg, class... Args>
717 size_t catsep(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
718 {
719  size_t num = to_chars(buf, a);
720  buf = buf.len >= num ? buf.sub(num) : substr{};
721  num += detail::catsep_more(buf, sep, more...);
722  return num;
723 }
724 
725 /** like c4::catsep() but return a substr instead of a size
726  * @see c4::catsep(). c4::uncatsep() is the inverse of c4::catsep(). */
727 template<class... Args>
728 substr catsep_sub(substr buf, Args && ...args)
729 {
730  size_t sz = catsep(buf, std::forward<Args>(args)...);
731  C4_CHECK(sz <= buf.len);
732  return {buf.str, sz <= buf.len ? sz : buf.len};
733 }
734 
735 /** @} */
736 
737 
738 
739 //-----------------------------------------------------------------------------
740 //-----------------------------------------------------------------------------
741 //-----------------------------------------------------------------------------
742 
743 /** @defgroup doc_uncatsep uncatsep: deserialize the separated arguments from a string
744  * @{ */
745 
746 /** deserialize the arguments from the given buffer.
747  *
748  * @return the number of characters read from the buffer, or csubstr::npos
749  * if a conversion was not successful.
750  * @see c4::cat(). c4::uncat() is the inverse of c4::cat(). */
751 
752 /** deserialize the arguments from the given buffer, using a separator.
753  *
754  * @return the number of characters read from the buffer, or csubstr::npos
755  * if a conversion was not successful
756  * @see c4::catsep(). c4::uncatsep() is the inverse of c4::catsep(). */
757 template<class Sep, class Arg, class... Args>
758 size_t uncatsep(csubstr buf, Sep & C4_RESTRICT sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
759 {
760  size_t ret = from_chars_first(buf, &a), num = ret;
761  if(C4_UNLIKELY(ret == csubstr::npos))
762  return csubstr::npos;
763  buf = buf.len >= ret ? buf.sub(ret) : substr{};
764  ret = detail::uncatsep_more(buf, sep, more...);
765  if(C4_UNLIKELY(ret == csubstr::npos))
766  return csubstr::npos;
767  num += ret;
768  return num;
769 }
770 
771 /** @} */
772 
773 
774 //-----------------------------------------------------------------------------
775 //-----------------------------------------------------------------------------
776 //-----------------------------------------------------------------------------
777 
778 /** @defgroup doc_format format: formatted string interpolation
779  * @{ */
780 
781 /// @cond dev
782 // terminates the variadic recursion
783 inline size_t format(substr buf, csubstr fmt)
784 {
785  return to_chars(buf, fmt);
786 }
787 /// @endcond
788 
789 
790 /** using a format string, serialize the arguments into the given
791  * fixed-size buffer.
792  * The buffer size is strictly respected: no writes will occur beyond its end.
793  * In the format string, each argument is marked with a compact
794  * curly-bracket pair: {}. Arguments beyond the last curly bracket pair
795  * are silently ignored. For example:
796  * @code{.cpp}
797  * c4::format(buf, "the {} drank {} {}", "partier", 5, "beers"); // the partier drank 5 beers
798  * c4::format(buf, "the {} drank {} {}", "programmer", 6, "coffees"); // the programmer drank 6 coffees
799  * @endcode
800  * @return the number of characters needed to write into the buffer.
801  * @see c4::formatrs() if instead of a fixed-size buffer, a resizeable container is desired
802  * @see c4::unformat() for the inverse function
803  * @see c4::cat() if no format or separator is needed
804  * @see c4::catsep() if no format is needed, but a separator must be used */
805 template<class Arg, class... Args>
806 size_t format(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
807 {
808  size_t pos = fmt.find("{}"); // @todo use _find_fmt()
809  if(C4_UNLIKELY(pos == csubstr::npos))
810  return to_chars(buf, fmt);
811  size_t num = to_chars(buf, fmt.sub(0, pos));
812  size_t out = num;
813  buf = buf.len >= num ? buf.sub(num) : substr{};
814  num = to_chars(buf, a);
815  out += num;
816  buf = buf.len >= num ? buf.sub(num) : substr{};
817  num = format(buf, fmt.sub(pos + 2), more...);
818  out += num;
819  return out;
820 }
821 
822 /** like c4::format() but return a substr instead of a size
823  * @see c4::format()
824  * @see c4::catsep(). uncatsep() is the inverse of catsep(). */
825 template<class... Args>
826 substr format_sub(substr buf, csubstr fmt, Args const& C4_RESTRICT ...args)
827 {
828  size_t sz = c4::format(buf, fmt, args...);
829  C4_CHECK(sz <= buf.len);
830  return {buf.str, sz <= buf.len ? sz : buf.len};
831 }
832 
833 /** @} */
834 
835 
836 //-----------------------------------------------------------------------------
837 
838 /** @defgroup doc_unformat unformat: formatted read from string
839  * @{ */
840 
841 /// @cond dev
842 // terminates the variadic recursion
843 inline size_t unformat(csubstr /*buf*/, csubstr fmt)
844 {
845  return fmt.len;
846 }
847 /// @endcond
848 
849 
850 /** using a format string, deserialize the arguments from the given
851  * buffer.
852  * @return the number of characters read from the buffer, or npos if a conversion failed.
853  * @see c4::format(). c4::unformat() is the inverse function to format(). */
854 template<class Arg, class... Args>
855 size_t unformat(csubstr buf, csubstr fmt, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
856 {
857  const size_t pos = fmt.find("{}");
858  if(C4_UNLIKELY(pos == csubstr::npos))
859  return unformat(buf, fmt);
860  size_t num = pos;
861  size_t out = num;
862  buf = buf.len >= num ? buf.sub(num) : substr{};
863  num = from_chars_first(buf, &a);
864  if(C4_UNLIKELY(num == csubstr::npos))
865  return csubstr::npos;
866  out += num;
867  buf = buf.len >= num ? buf.sub(num) : substr{};
868  num = unformat(buf, fmt.sub(pos + 2), more...);
869  if(C4_UNLIKELY(num == csubstr::npos))
870  return csubstr::npos;
871  out += num;
872  return out;
873 }
874 
875 /** @} */
876 
877 
878 //-----------------------------------------------------------------------------
879 //-----------------------------------------------------------------------------
880 //-----------------------------------------------------------------------------
881 
882 /** cat+resize: like c4::cat(), but receives a container, and resizes
883  * it as needed to contain the result. The container is
884  * overwritten. To append to it, use the append overload.
885  * @see c4::cat()
886  * @ingroup doc_cat */
887 template<class CharOwningContainer, class... Args>
888 inline void catrs(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args)
889 {
890 retry:
891  substr buf = to_substr(*cont);
892  size_t ret = cat(buf, args...);
893  cont->resize(ret);
894  if(ret > buf.len)
895  goto retry;
896 }
897 
898 /** cat+resize: like c4::cat(), but creates and returns a new
899  * container sized as needed to contain the result.
900  * @see c4::cat()
901  * @ingroup doc_cat */
902 template<class CharOwningContainer, class... Args>
903 inline CharOwningContainer catrs(Args const& C4_RESTRICT ...args)
904 {
905  CharOwningContainer cont;
906  catrs(&cont, args...);
907  return cont;
908 }
909 
910 /** cat+resize+append: like c4::cat(), but receives a container, and
911  * appends to it instead of overwriting it. The container is resized
912  * as needed to contain the result.
913  *
914  * @return the region newly appended to the original container
915  * @see c4::cat()
916  * @see c4::catrs()
917  * @ingroup doc_cat */
918 template<class CharOwningContainer, class... Args>
919 inline csubstr catrs_append(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args)
920 {
921  const size_t pos = cont->size();
922 retry:
923  substr buf = to_substr(*cont).sub(pos);
924  size_t ret = cat(buf, args...);
925  cont->resize(pos + ret);
926  if(ret > buf.len)
927  goto retry;
928  return to_csubstr(*cont).range(pos, cont->size());
929 }
930 
931 
932 //-----------------------------------------------------------------------------
933 
934 /** catsep+resize: like c4::catsep(), but receives a container, and
935  * resizes it as needed to contain the result. The container is
936  * overwritten. To append to the container use the append overload.
937  *
938  * @see c4::catsep()
939  * @ingroup doc_catsep */
940 template<class CharOwningContainer, class Sep, class... Args>
941 inline void catseprs(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
942 {
943 retry:
944  substr buf = to_substr(*cont);
945  size_t ret = catsep(buf, sep, args...);
946  cont->resize(ret);
947  if(ret > buf.len)
948  goto retry;
949 }
950 
951 /** catsep+resize: like c4::catsep(), but create a new container with
952  * the result.
953  *
954  * @return the requested container
955  * @ingroup doc_catsep */
956 template<class CharOwningContainer, class Sep, class... Args>
957 inline CharOwningContainer catseprs(Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
958 {
959  CharOwningContainer cont;
960  catseprs(&cont, sep, args...);
961  return cont;
962 }
963 
964 
965 /** catsep+resize+append: like catsep(), but receives a container, and
966  * appends the arguments, resizing the container as needed to contain
967  * the result. The buffer is appended to.
968  *
969  * @return a csubstr of the appended part
970  * @ingroup formatting_functions
971  * @ingroup doc_catsep */
972 template<class CharOwningContainer, class Sep, class... Args>
973 inline csubstr catseprs_append(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
974 {
975  const size_t pos = cont->size();
976 retry:
977  substr buf = to_substr(*cont).sub(pos);
978  size_t ret = catsep(buf, sep, args...);
979  cont->resize(pos + ret);
980  if(ret > buf.len)
981  goto retry;
982  return to_csubstr(*cont).range(pos, cont->size());
983 }
984 
985 
986 //-----------------------------------------------------------------------------
987 
988 /** format+resize: like c4::format(), but receives a container, and
989  * resizes it as needed to contain the result. The container is
990  * overwritten. To append to the container use the append overload.
991  *
992  * @see c4::format()
993  * @ingroup doc_format */
994 template<class CharOwningContainer, class... Args>
995 inline void formatrs(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args)
996 {
997 retry:
998  substr buf = to_substr(*cont);
999  size_t ret = format(buf, fmt, args...);
1000  cont->resize(ret);
1001  if(ret > buf.len)
1002  goto retry;
1003 }
1004 
1005 /** format+resize: like c4::format(), but create a new container with
1006  * the result.
1007  *
1008  * @return the requested container
1009  * @ingroup doc_format */
1010 template<class CharOwningContainer, class... Args>
1011 inline CharOwningContainer formatrs(csubstr fmt, Args const& C4_RESTRICT ...args)
1012 {
1013  CharOwningContainer cont;
1014  formatrs(&cont, fmt, args...);
1015  return cont;
1016 }
1017 
1018 /** format+resize+append: like format(), but receives a container, and appends the
1019  * arguments, resizing the container as needed to contain the
1020  * result. The buffer is appended to.
1021  * @return the region newly appended to the original container
1022  * @ingroup formatting_functions
1023  * @ingroup doc_format */
1024 template<class CharOwningContainer, class... Args>
1025 inline csubstr formatrs_append(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args)
1026 {
1027  const size_t pos = cont->size();
1028 retry:
1029  substr buf = to_substr(*cont).sub(pos);
1030  size_t ret = format(buf, fmt, args...);
1031  cont->resize(pos + ret);
1032  if(ret > buf.len)
1033  goto retry;
1034  return to_csubstr(*cont).range(pos, cont->size());
1035 }
1036 
1037 /** @} */
1038 
1039 } // namespace c4
1040 
1041 #ifdef _MSC_VER
1042 # pragma warning(pop)
1043 #elif defined(__clang__)
1044 # pragma clang diagnostic pop
1045 #elif defined(__GNUC__)
1046 # pragma GCC diagnostic pop
1047 #endif
1048 
1049 #endif /* _C4_FORMAT_HPP_ */
Lightweight generic type-safe wrappers for converting individual values to/from strings.
left_< T > left(T val, size_t width, char padchar=' ')
mark an argument to be aligned left
Definition: format.hpp:515
right_< T > right(T val, size_t width, char padchar=' ')
mark an argument to be aligned right
Definition: format.hpp:522
bool atox(csubstr s, uint8_t *v) noexcept
Definition: charconv.hpp:2288
boolalpha_< T > boolalpha(T const &val, bool strict_read=false)
Definition: format.hpp:72
substr cat_sub(substr buf, Args &&...args)
like c4::cat() but return a substr instead of a size
Definition: format.hpp:594
csubstr catrs_append(CharOwningContainer *cont, Args const &...args)
cat+resize+append: like c4::cat(), but receives a container, and appends to it instead of overwriting...
Definition: format.hpp:919
size_t cat(substr buf, Arg const &a, Args const &...more)
serialize the arguments, concatenating them to the given fixed-size buffer.
Definition: format.hpp:584
void catrs(CharOwningContainer *cont, Args const &...args)
cat+resize: like c4::cat(), but receives a container, and resizes it as needed to contain the result.
Definition: format.hpp:888
csubstr catseprs_append(CharOwningContainer *cont, Sep const &sep, Args const &...args)
catsep+resize+append: like catsep(), but receives a container, and appends the arguments,...
Definition: format.hpp:973
void catseprs(CharOwningContainer *cont, Sep const &sep, Args const &...args)
catsep+resize: like c4::catsep(), but receives a container, and resizes it as needed to contain the r...
Definition: format.hpp:941
size_t catsep(substr buf, Sep const &sep, Arg const &a, Args const &...more)
serialize the arguments, concatenating them to the given fixed-size buffer, using a separator between...
Definition: format.hpp:717
substr catsep_sub(substr buf, Args &&...args)
like c4::catsep() but return a substr instead of a size
Definition: format.hpp:728
RealFormat_e
Definition: charconv.hpp:200
@ FTOA_FLOAT
print the real number in floating point format (like f)
Definition: charconv.hpp:202
size_t dtoa(substr str, double v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept
Convert a double-precision real number to string.
Definition: charconv.hpp:2087
csubstr formatrs_append(CharOwningContainer *cont, csubstr fmt, Args const &...args)
format+resize+append: like format(), but receives a container, and appends the arguments,...
Definition: format.hpp:1025
substr format_sub(substr buf, csubstr fmt, Args const &...args)
like c4::format() but return a substr instead of a size
Definition: format.hpp:826
size_t format(substr buf, csubstr fmt, Arg const &a, Args const &...more)
using a format string, serialize the arguments into the given fixed-size buffer.
Definition: format.hpp:806
void formatrs(CharOwningContainer *cont, csubstr fmt, Args const &...args)
format+resize: like c4::format(), but receives a container, and resizes it as needed to contain the r...
Definition: format.hpp:995
size_t from_chars_first(csubstr buf, uint8_t *v) noexcept
Definition: charconv.hpp:2391
bool from_chars(csubstr buf, uint8_t *v) noexcept
Definition: charconv.hpp:2363
size_t ftoa(substr str, float v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept
Convert a single-precision real number to string.
Definition: charconv.hpp:2061
integral_< intptr_t > hex(std::nullptr_t)
format null as an hexadecimal value
Definition: format.hpp:163
integral_< T > integral(T val, T radix=10)
format an integral type with a custom radix
Definition: format.hpp:131
integral_< intptr_t > bin(std::nullptr_t)
format null as a binary 0-1 value
Definition: format.hpp:215
integral_< intptr_t > oct(std::nullptr_t)
format null as an octal value
Definition: format.hpp:188
size_t itoa(substr buf, T v) noexcept
convert an integral signed decimal to a string.
Definition: charconv.hpp:1109
overflow_checked_< T > overflow_checked(T &val)
Definition: format.hpp:276
const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t))
mark a variable to be written in raw binary format, using memcpy
Definition: format.hpp:416
raw_wrapper_< byte > raw_wrapper
Definition: format.hpp:406
raw_wrapper_< cbyte > const_raw_wrapper
Definition: format.hpp:405
const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t))
mark a variable to be written in raw binary format, using memcpy
Definition: format.hpp:410
real_< T > real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
Definition: format.hpp:360
csubstr to_csubstr(substr s) noexcept
neutral version for use in generic code
Definition: substr.hpp:2189
substr to_substr(substr s) noexcept
neutral version for use in generic code
Definition: substr.hpp:2187
size_t to_chars(substr buf, uint8_t v) noexcept
Definition: charconv.hpp:2328
size_t uncat(csubstr buf, Arg &a, Args &...more)
deserialize the arguments from the given buffer.
Definition: format.hpp:625
size_t uncatsep(csubstr buf, Sep &sep, Arg &a, Args &...more)
deserialize the arguments from the given buffer.
Definition: format.hpp:758
size_t unformat(csubstr buf, csubstr fmt, Arg &a, Args &...more)
using a format string, deserialize the arguments from the given buffer.
Definition: format.hpp:855
size_t utoa(substr buf, T v) noexcept
convert an integral unsigned decimal to a string.
Definition: charconv.hpp:1304
integral_padded_< T > zpad(T val, size_t num_digits)
pad the argument with zeroes on the left, with decimal radix
Definition: format.hpp:235
@ npos
a null string position
Definition: common.hpp:266
Definition: common.cpp:12
write a variable as an alphabetic boolean, ie as either true or false
Definition: format.hpp:65
boolalpha_(T val_, bool strict_read_=false)
Definition: format.hpp:66
format an integral type with a custom radix
Definition: format.hpp:110
C4_STATIC_ASSERT(std::is_integral< T >::value)
integral_(T val_, T radix_)
Definition: format.hpp:114
format an integral type with a custom radix, and pad with zeroes on the left
Definition: format.hpp:120
integral_padded_(T val_, T radix_, size_t nd)
Definition: format.hpp:125
C4_STATIC_ASSERT(std::is_integral< T >::value)
left_(T v, size_t w, char p)
Definition: format.hpp:501
size_t width
Definition: format.hpp:499
raw_wrapper_(blob_< T > data, size_t alignment_) noexcept
Definition: format.hpp:396
RealFormat_e fmt
Definition: format.hpp:355
real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT)
Definition: format.hpp:356
size_t width
Definition: format.hpp:508
right_(T v, size_t w, char p)
Definition: format.hpp:510