rapidyaml  0.7.2
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 integer type, detecting overflow (returns false on overflow)
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 /** read an integer type, detecting overflow (returns false on overflow)
336  * @ingroup doc_from_chars */
337 template<class T>
338 C4_ALWAYS_INLINE bool from_chars(csubstr s, fmt::overflow_checked_<T> *wrapper)
339 {
340  if(C4_LIKELY(!overflows<T>(s)))
341  return atox(s, wrapper->val);
342  return false;
343 }
344 
345 
346 //-----------------------------------------------------------------------------
347 //-----------------------------------------------------------------------------
348 //-----------------------------------------------------------------------------
349 // formatting real types
350 
351 namespace fmt {
352 
353 /** @addtogroup doc_format_specifiers
354  * @{ */
355 
356 /** @defgroup doc_real_specifiers Real specifiers
357  * @{ */
358 
359 template<class T>
360 struct real_
361 {
362  T val;
365  real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT) : val(v), precision(prec), fmt(f) {}
366 };
367 
368 template<class T>
369 real_<T> real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
370 {
371  return real_<T>(val, precision, fmt);
372 }
373 
374 /** @} */ // real_specifiers
375 
376 /** @} */ // format_specifiers
377 
378 } // namespace fmt
379 
380 /** @ingroup doc_to_chars */
381 inline size_t to_chars(substr buf, fmt::real_< float> fmt) { return ftoa(buf, fmt.val, fmt.precision, fmt.fmt); }
382 /** @ingroup doc_to_chars */
383 inline size_t to_chars(substr buf, fmt::real_<double> fmt) { return dtoa(buf, fmt.val, fmt.precision, fmt.fmt); }
384 
385 
386 //-----------------------------------------------------------------------------
387 //-----------------------------------------------------------------------------
388 //-----------------------------------------------------------------------------
389 // writing raw binary data
390 
391 namespace fmt {
392 
393 /** @addtogroup doc_format_specifiers
394  * @{ */
395 
396 /** @defgroup doc_raw_binary_specifiers Raw binary data
397  * @{ */
398 
399 /** @see blob_ */
400 template<class T>
401 struct raw_wrapper_ : public blob_<T>
402 {
403  size_t alignment;
404 
405  C4_ALWAYS_INLINE raw_wrapper_(blob_<T> data, size_t alignment_) noexcept
406  :
407  blob_<T>(data),
408  alignment(alignment_)
409  {
410  C4_ASSERT_MSG(alignment > 0 && (alignment & (alignment - 1)) == 0, "alignment must be a power of two");
411  }
412 };
413 
416 
417 /** mark a variable to be written in raw binary format, using memcpy
418  * @see blob_ */
419 inline const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t))
420 {
421  return const_raw_wrapper(data, alignment);
422 }
423 /** mark a variable to be written in raw binary format, using memcpy
424  * @see blob_ */
425 inline const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t))
426 {
427  return const_raw_wrapper(data, alignment);
428 }
429 /** mark a variable to be written in raw binary format, using memcpy
430  * @see blob_ */
431 template<class T>
432 inline const_raw_wrapper craw(T const& C4_RESTRICT data, size_t alignment=alignof(T))
433 {
434  return const_raw_wrapper(cblob(data), alignment);
435 }
436 /** mark a variable to be written in raw binary format, using memcpy
437  * @see blob_ */
438 template<class T>
439 inline const_raw_wrapper raw(T const& C4_RESTRICT data, size_t alignment=alignof(T))
440 {
441  return const_raw_wrapper(cblob(data), alignment);
442 }
443 
444 /** mark a variable to be read in raw binary format, using memcpy */
445 inline raw_wrapper raw(blob data, size_t alignment=alignof(max_align_t))
446 {
447  return raw_wrapper(data, alignment);
448 }
449 /** mark a variable to be read in raw binary format, using memcpy */
450 template<class T>
451 inline raw_wrapper raw(T & C4_RESTRICT data, size_t alignment=alignof(T))
452 {
453  return raw_wrapper(blob(data), alignment);
454 }
455 
456 /** @} */ // raw_binary_specifiers
457 
458 /** @} */ // format_specifiers
459 
460 } // namespace fmt
461 
462 
463 /** write a variable in raw binary format, using memcpy
464  * @ingroup doc_to_chars */
465 C4CORE_EXPORT size_t to_chars(substr buf, fmt::const_raw_wrapper r);
466 
467 /** read a variable in raw binary format, using memcpy
468  * @ingroup doc_from_chars */
469 C4CORE_EXPORT bool from_chars(csubstr buf, fmt::raw_wrapper *r);
470 /** read a variable in raw binary format, using memcpy
471  * @ingroup doc_from_chars */
472 inline bool from_chars(csubstr buf, fmt::raw_wrapper r)
473 {
474  return from_chars(buf, &r);
475 }
476 
477 /** read a variable in raw binary format, using memcpy
478  * @ingroup doc_from_chars_first */
479 inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper *r)
480 {
481  return from_chars(buf, r);
482 }
483 /** read a variable in raw binary format, using memcpy
484  * @ingroup doc_from_chars_first */
485 inline size_t from_chars_first(csubstr buf, fmt::raw_wrapper r)
486 {
487  return from_chars(buf, &r);
488 }
489 
490 
491 //-----------------------------------------------------------------------------
492 //-----------------------------------------------------------------------------
493 //-----------------------------------------------------------------------------
494 // formatting aligned to left/right
495 
496 namespace fmt {
497 
498 /** @addtogroup doc_format_specifiers
499  * @{ */
500 
501 /** @defgroup doc_alignment_specifiers Alignment specifiers
502  * @{ */
503 
504 template<class T>
505 struct left_
506 {
507  T val;
508  size_t width;
509  char pad;
510  left_(T v, size_t w, char p) : val(v), width(w), pad(p) {}
511 };
512 
513 template<class T>
514 struct right_
515 {
516  T val;
517  size_t width;
518  char pad;
519  right_(T v, size_t w, char p) : val(v), width(w), pad(p) {}
520 };
521 
522 /** mark an argument to be aligned left */
523 template<class T>
524 left_<T> left(T val, size_t width, char padchar=' ')
525 {
526  return left_<T>(val, width, padchar);
527 }
528 
529 /** mark an argument to be aligned right */
530 template<class T>
531 right_<T> right(T val, size_t width, char padchar=' ')
532 {
533  return right_<T>(val, width, padchar);
534 }
535 
536 /** @} */ // alignment_specifiers
537 
538 /** @} */ // format_specifiers
539 
540 } // namespace fmt
541 
542 
543 /** @ingroup doc_to_chars */
544 template<class T>
545 size_t to_chars(substr buf, fmt::left_<T> const& C4_RESTRICT align)
546 {
547  size_t ret = to_chars(buf, align.val);
548  if(ret >= buf.len || ret >= align.width)
549  return ret > align.width ? ret : align.width;
550  buf.first(align.width).sub(ret).fill(align.pad);
551  to_chars(buf, align.val);
552  return align.width;
553 }
554 
555 /** @ingroup doc_to_chars */
556 template<class T>
557 size_t to_chars(substr buf, fmt::right_<T> const& C4_RESTRICT align)
558 {
559  size_t ret = to_chars(buf, align.val);
560  if(ret >= buf.len || ret >= align.width)
561  return ret > align.width ? ret : align.width;
562  size_t rem = static_cast<size_t>(align.width - ret);
563  buf.first(rem).fill(align.pad);
564  to_chars(buf.sub(rem), align.val);
565  return align.width;
566 }
567 
568 
569 //-----------------------------------------------------------------------------
570 //-----------------------------------------------------------------------------
571 //-----------------------------------------------------------------------------
572 
573 /** @defgroup doc_cat cat: concatenate arguments to string
574  * @{ */
575 
576 /** @cond dev */
577 // terminates the variadic recursion
578 inline size_t cat(substr /*buf*/)
579 {
580  return 0;
581 }
582 /** @endcond */
583 
584 
585 /** serialize the arguments, concatenating them to the given fixed-size buffer.
586  * The buffer size is strictly respected: no writes will occur beyond its end.
587  * @return the number of characters needed to write all the arguments into the buffer.
588  * @see c4::catrs() if instead of a fixed-size buffer, a resizeable container is desired
589  * @see c4::uncat() for the inverse function
590  * @see c4::catsep() if a separator between each argument is to be used
591  * @see c4::format() if a format string is desired */
592 template<class Arg, class... Args>
593 size_t cat(substr buf, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
594 {
595  size_t num = to_chars(buf, a);
596  buf = buf.len >= num ? buf.sub(num) : substr{};
597  num += cat(buf, more...);
598  return num;
599 }
600 
601 /** like c4::cat() but return a substr instead of a size */
602 template<class... Args>
603 substr cat_sub(substr buf, Args && ...args)
604 {
605  size_t sz = cat(buf, std::forward<Args>(args)...);
606  C4_CHECK(sz <= buf.len);
607  return {buf.str, sz <= buf.len ? sz : buf.len};
608 }
609 
610 /** @} */
611 
612 
613 //-----------------------------------------------------------------------------
614 
615 
616 /** @defgroup doc_uncat uncat: read concatenated arguments from string
617  * @{ */
618 
619 /** @cond dev */
620 // terminates the variadic recursion
621 inline size_t uncat(csubstr /*buf*/)
622 {
623  return 0;
624 }
625 /** @endcond */
626 
627 
628 /** deserialize the arguments from the given buffer.
629  *
630  * @return the number of characters read from the buffer, or csubstr::npos
631  * if a conversion was not successful.
632  * @see c4::cat(). c4::uncat() is the inverse of c4::cat(). */
633 template<class Arg, class... Args>
634 size_t uncat(csubstr buf, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
635 {
636  size_t out = from_chars_first(buf, &a);
637  if(C4_UNLIKELY(out == csubstr::npos))
638  return csubstr::npos;
639  buf = buf.len >= out ? buf.sub(out) : substr{};
640  size_t num = uncat(buf, more...);
641  if(C4_UNLIKELY(num == csubstr::npos))
642  return csubstr::npos;
643  return out + num;
644 }
645 
646 /** @} */
647 
648 
649 
650 //-----------------------------------------------------------------------------
651 //-----------------------------------------------------------------------------
652 //-----------------------------------------------------------------------------
653 
654 
655 /** @defgroup doc_catsep catsep: cat arguments to string with separator
656  * @{ */
657 
658 /** @cond dev */
659 namespace detail {
660 template<class Sep>
661 C4_ALWAYS_INLINE size_t catsep_more(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/)
662 {
663  return 0;
664 }
665 
666 template<class Sep, class Arg, class... Args>
667 size_t catsep_more(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
668 {
669  size_t ret = to_chars(buf, sep);
670  size_t num = ret;
671  buf = buf.len >= ret ? buf.sub(ret) : substr{};
672  ret = to_chars(buf, a);
673  num += ret;
674  buf = buf.len >= ret ? buf.sub(ret) : substr{};
675  ret = catsep_more(buf, sep, more...);
676  num += ret;
677  return num;
678 }
679 
680 
681 template<class Sep>
682 inline size_t uncatsep_more(csubstr /*buf*/, Sep & /*sep*/)
683 {
684  return 0;
685 }
686 
687 template<class Sep, class Arg, class... Args>
688 size_t uncatsep_more(csubstr buf, Sep & C4_RESTRICT sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
689 {
690  size_t ret = from_chars_first(buf, &sep);
691  size_t num = ret;
692  if(C4_UNLIKELY(ret == csubstr::npos))
693  return csubstr::npos;
694  buf = buf.len >= ret ? buf.sub(ret) : substr{};
695  ret = from_chars_first(buf, &a);
696  if(C4_UNLIKELY(ret == csubstr::npos))
697  return csubstr::npos;
698  num += ret;
699  buf = buf.len >= ret ? buf.sub(ret) : substr{};
700  ret = uncatsep_more(buf, sep, more...);
701  if(C4_UNLIKELY(ret == csubstr::npos))
702  return csubstr::npos;
703  num += ret;
704  return num;
705 }
706 
707 } // namespace detail
708 
709 template<class Sep>
710 size_t catsep(substr /*buf*/, Sep const& C4_RESTRICT /*sep*/)
711 {
712  return 0;
713 }
714 /** @endcond */
715 
716 
717 /** serialize the arguments, concatenating them to the given fixed-size
718  * buffer, using a separator between each argument.
719  * The buffer size is strictly respected: no writes will occur beyond its end.
720  * @return the number of characters needed to write all the arguments into the buffer.
721  * @see c4::catseprs() if instead of a fixed-size buffer, a resizeable container is desired
722  * @see c4::uncatsep() for the inverse function (ie, reading instead of writing)
723  * @see c4::cat() if no separator is needed
724  * @see c4::format() if a format string is desired */
725 template<class Sep, class Arg, class... Args>
726 size_t catsep(substr buf, Sep const& C4_RESTRICT sep, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
727 {
728  size_t num = to_chars(buf, a);
729  buf = buf.len >= num ? buf.sub(num) : substr{};
730  num += detail::catsep_more(buf, sep, more...);
731  return num;
732 }
733 
734 /** like c4::catsep() but return a substr instead of a size
735  * @see c4::catsep(). c4::uncatsep() is the inverse of c4::catsep(). */
736 template<class... Args>
737 substr catsep_sub(substr buf, Args && ...args)
738 {
739  size_t sz = catsep(buf, std::forward<Args>(args)...);
740  C4_CHECK(sz <= buf.len);
741  return {buf.str, sz <= buf.len ? sz : buf.len};
742 }
743 
744 /** @} */
745 
746 
747 
748 //-----------------------------------------------------------------------------
749 //-----------------------------------------------------------------------------
750 //-----------------------------------------------------------------------------
751 
752 /** @defgroup doc_uncatsep uncatsep: deserialize the separated arguments from a string
753  * @{ */
754 
755 /** deserialize the arguments from the given buffer.
756  *
757  * @return the number of characters read from the buffer, or csubstr::npos
758  * if a conversion was not successful.
759  * @see c4::cat(). c4::uncat() is the inverse of c4::cat(). */
760 
761 /** deserialize the arguments from the given buffer, using a separator.
762  *
763  * @return the number of characters read from the buffer, or csubstr::npos
764  * if a conversion was not successful
765  * @see c4::catsep(). c4::uncatsep() is the inverse of c4::catsep(). */
766 template<class Sep, class Arg, class... Args>
767 size_t uncatsep(csubstr buf, Sep & C4_RESTRICT sep, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
768 {
769  size_t ret = from_chars_first(buf, &a), num = ret;
770  if(C4_UNLIKELY(ret == csubstr::npos))
771  return csubstr::npos;
772  buf = buf.len >= ret ? buf.sub(ret) : substr{};
773  ret = detail::uncatsep_more(buf, sep, more...);
774  if(C4_UNLIKELY(ret == csubstr::npos))
775  return csubstr::npos;
776  num += ret;
777  return num;
778 }
779 
780 /** @} */
781 
782 
783 //-----------------------------------------------------------------------------
784 //-----------------------------------------------------------------------------
785 //-----------------------------------------------------------------------------
786 
787 /** @defgroup doc_format format: formatted string interpolation
788  * @{ */
789 
790 /// @cond dev
791 // terminates the variadic recursion
792 inline size_t format(substr buf, csubstr fmt)
793 {
794  return to_chars(buf, fmt);
795 }
796 /// @endcond
797 
798 
799 /** using a format string, serialize the arguments into the given
800  * fixed-size buffer.
801  * The buffer size is strictly respected: no writes will occur beyond its end.
802  * In the format string, each argument is marked with a compact
803  * curly-bracket pair: {}. Arguments beyond the last curly bracket pair
804  * are silently ignored. For example:
805  * @code{.cpp}
806  * c4::format(buf, "the {} drank {} {}", "partier", 5, "beers"); // the partier drank 5 beers
807  * c4::format(buf, "the {} drank {} {}", "programmer", 6, "coffees"); // the programmer drank 6 coffees
808  * @endcode
809  * @return the number of characters needed to write into the buffer.
810  * @see c4::formatrs() if instead of a fixed-size buffer, a resizeable container is desired
811  * @see c4::unformat() for the inverse function
812  * @see c4::cat() if no format or separator is needed
813  * @see c4::catsep() if no format is needed, but a separator must be used */
814 template<class Arg, class... Args>
815 size_t format(substr buf, csubstr fmt, Arg const& C4_RESTRICT a, Args const& C4_RESTRICT ...more)
816 {
817  size_t pos = fmt.find("{}"); // @todo use _find_fmt()
818  if(C4_UNLIKELY(pos == csubstr::npos))
819  return to_chars(buf, fmt);
820  size_t num = to_chars(buf, fmt.sub(0, pos));
821  size_t out = num;
822  buf = buf.len >= num ? buf.sub(num) : substr{};
823  num = to_chars(buf, a);
824  out += num;
825  buf = buf.len >= num ? buf.sub(num) : substr{};
826  num = format(buf, fmt.sub(pos + 2), more...);
827  out += num;
828  return out;
829 }
830 
831 /** like c4::format() but return a substr instead of a size
832  * @see c4::format()
833  * @see c4::catsep(). uncatsep() is the inverse of catsep(). */
834 template<class... Args>
835 substr format_sub(substr buf, csubstr fmt, Args const& C4_RESTRICT ...args)
836 {
837  size_t sz = c4::format(buf, fmt, args...);
838  C4_CHECK(sz <= buf.len);
839  return {buf.str, sz <= buf.len ? sz : buf.len};
840 }
841 
842 /** @} */
843 
844 
845 //-----------------------------------------------------------------------------
846 
847 /** @defgroup doc_unformat unformat: formatted read from string
848  * @{ */
849 
850 /// @cond dev
851 // terminates the variadic recursion
852 inline size_t unformat(csubstr /*buf*/, csubstr fmt)
853 {
854  return fmt.len;
855 }
856 /// @endcond
857 
858 
859 /** using a format string, deserialize the arguments from the given
860  * buffer.
861  * @return the number of characters read from the buffer, or npos if a conversion failed.
862  * @see c4::format(). c4::unformat() is the inverse function to format(). */
863 template<class Arg, class... Args>
864 size_t unformat(csubstr buf, csubstr fmt, Arg & C4_RESTRICT a, Args & C4_RESTRICT ...more)
865 {
866  const size_t pos = fmt.find("{}");
867  if(C4_UNLIKELY(pos == csubstr::npos))
868  return unformat(buf, fmt);
869  size_t num = pos;
870  size_t out = num;
871  buf = buf.len >= num ? buf.sub(num) : substr{};
872  num = from_chars_first(buf, &a);
873  if(C4_UNLIKELY(num == csubstr::npos))
874  return csubstr::npos;
875  out += num;
876  buf = buf.len >= num ? buf.sub(num) : substr{};
877  num = unformat(buf, fmt.sub(pos + 2), more...);
878  if(C4_UNLIKELY(num == csubstr::npos))
879  return csubstr::npos;
880  out += num;
881  return out;
882 }
883 
884 /** @} */
885 
886 
887 //-----------------------------------------------------------------------------
888 //-----------------------------------------------------------------------------
889 //-----------------------------------------------------------------------------
890 
891 /** cat+resize: like c4::cat(), but receives a container, and resizes
892  * it as needed to contain the result. The container is
893  * overwritten. To append to it, use the append overload.
894  * @see c4::cat()
895  * @ingroup doc_cat */
896 template<class CharOwningContainer, class... Args>
897 inline void catrs(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args)
898 {
899 retry:
900  substr buf = to_substr(*cont);
901  size_t ret = cat(buf, args...);
902  cont->resize(ret);
903  if(ret > buf.len)
904  goto retry;
905 }
906 
907 /** cat+resize: like c4::cat(), but creates and returns a new
908  * container sized as needed to contain the result.
909  * @see c4::cat()
910  * @ingroup doc_cat */
911 template<class CharOwningContainer, class... Args>
912 inline CharOwningContainer catrs(Args const& C4_RESTRICT ...args)
913 {
914  CharOwningContainer cont;
915  catrs(&cont, args...);
916  return cont;
917 }
918 
919 /** cat+resize+append: like c4::cat(), but receives a container, and
920  * appends to it instead of overwriting it. The container is resized
921  * as needed to contain the result.
922  *
923  * @return the region newly appended to the original container
924  * @see c4::cat()
925  * @see c4::catrs()
926  * @ingroup doc_cat */
927 template<class CharOwningContainer, class... Args>
928 inline csubstr catrs_append(CharOwningContainer * C4_RESTRICT cont, Args const& C4_RESTRICT ...args)
929 {
930  const size_t pos = cont->size();
931 retry:
932  substr buf = to_substr(*cont).sub(pos);
933  size_t ret = cat(buf, args...);
934  cont->resize(pos + ret);
935  if(ret > buf.len)
936  goto retry;
937  return to_csubstr(*cont).range(pos, cont->size());
938 }
939 
940 
941 //-----------------------------------------------------------------------------
942 
943 /** catsep+resize: like c4::catsep(), but receives a container, and
944  * resizes it as needed to contain the result. The container is
945  * overwritten. To append to the container use the append overload.
946  *
947  * @see c4::catsep()
948  * @ingroup doc_catsep */
949 template<class CharOwningContainer, class Sep, class... Args>
950 inline void catseprs(CharOwningContainer * C4_RESTRICT cont, Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
951 {
952 retry:
953  substr buf = to_substr(*cont);
954  size_t ret = catsep(buf, sep, args...);
955  cont->resize(ret);
956  if(ret > buf.len)
957  goto retry;
958 }
959 
960 /** catsep+resize: like c4::catsep(), but create a new container with
961  * the result.
962  *
963  * @return the requested container
964  * @ingroup doc_catsep */
965 template<class CharOwningContainer, class Sep, class... Args>
966 inline CharOwningContainer catseprs(Sep const& C4_RESTRICT sep, Args const& C4_RESTRICT ...args)
967 {
968  CharOwningContainer cont;
969  catseprs(&cont, sep, args...);
970  return cont;
971 }
972 
973 
974 /** catsep+resize+append: like catsep(), but receives a container, and
975  * appends the arguments, resizing the container as needed to contain
976  * the result. The buffer is appended to.
977  *
978  * @return a csubstr of the appended part
979  * @ingroup formatting_functions
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 formatting_functions
1032  * @ingroup doc_format */
1033 template<class CharOwningContainer, class... Args>
1034 inline csubstr formatrs_append(CharOwningContainer * C4_RESTRICT cont, csubstr fmt, Args const& C4_RESTRICT ...args)
1035 {
1036  const size_t pos = cont->size();
1037 retry:
1038  substr buf = to_substr(*cont).sub(pos);
1039  size_t ret = format(buf, fmt, args...);
1040  cont->resize(pos + ret);
1041  if(ret > buf.len)
1042  goto retry;
1043  return to_csubstr(*cont).range(pos, cont->size());
1044 }
1045 
1046 /** @} */
1047 
1048 } // namespace c4
1049 
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:524
right_< T > right(T val, size_t width, char padchar=' ')
mark an argument to be aligned right
Definition: format.hpp:531
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:603
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:928
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:593
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:897
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:950
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:726
substr catsep_sub(substr buf, Args &&...args)
like c4::catsep() but return a substr instead of a size
Definition: format.hpp:737
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:1034
substr format_sub(substr buf, csubstr fmt, Args const &...args)
like c4::format() but return a substr instead of a size
Definition: format.hpp:835
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:815
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: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:425
raw_wrapper_< byte > raw_wrapper
Definition: format.hpp:415
raw_wrapper_< cbyte > const_raw_wrapper
Definition: format.hpp:414
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:419
real_< T > real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
Definition: format.hpp:369
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:634
size_t uncatsep(csubstr buf, Sep &sep, Arg &a, Args &...more)
deserialize the arguments from the given buffer.
Definition: format.hpp:767
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:864
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:510
size_t width
Definition: format.hpp:508
raw_wrapper_(blob_< T > data, size_t alignment_) noexcept
Definition: format.hpp:405
RealFormat_e fmt
Definition: format.hpp:364
real_(T v, int prec=-1, RealFormat_e f=FTOA_FLOAT)
Definition: format.hpp:365
size_t width
Definition: format.hpp:517
right_(T v, size_t w, char p)
Definition: format.hpp:519