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