jaulib v1.4.1-14-g15926ba
Jau Support Library (C++, Java, ..)
Loading...
Searching...
No Matches
string_cfmt.hpp
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021-2026 Gothel Software e.K.
4 *
5 * ***
6 *
7 * SPDX-License-Identifier: MIT
8 *
9 * This Source Code Form is subject to the terms of the MIT License
10 * If a copy of the MIT was not distributed with this
11 * file, You can obtain one at https://opensource.org/license/mit/.
12 *
13 */
14
15#ifndef JAU_STRING_CFMT_HPP_
16#define JAU_STRING_CFMT_HPP_
17
18#include <sys/types.h>
19#include <cassert>
20#include <cerrno>
21#include <concepts>
22#include <cstdarg>
23#include <cstddef>
24#include <cstdint>
25#include <cstdio>
26#include <cstdlib>
27#include <cstring>
28#include <limits>
29#include <ostream>
30#include <string>
31#include <string_view>
32#include <type_traits>
33#include <iostream>
34
35#include <jau/base_math.hpp>
36#include <jau/byte_util.hpp>
37#include <jau/cpp_lang_util.hpp>
38#include <jau/int_math.hpp>
39#include <jau/int_types.hpp>
40#include <jau/exceptions.hpp>
41#include <jau/float_types.hpp>
44#include <jau/type_concepts.hpp>
45#include <jau/string_util.hpp>
46#include <jau/cpp_pragma.hpp>
47#include <jau/enum_util.hpp>
48
49/**
50 * @anchor jau_cfmt_header
51 * ## jau::cfmt, a snprintf compliant runtime string format and compile-time validator
52 *
53 * ### Features
54 * - Compile type validation of arguments against the format string
55 * - Possible via `consteval` capable `constexpr` implementation
56 * - Live response via `static_assert` expressions within your IDE
57 * - Example using `jau_string_check(fmt, ...)` macro utilizing `jau::cfmt::check2`
58 * The macro resolves the passed arguments types via `decltype` to be utilized for `jau::cfmt::check2`
59 * ```
60 * jau_string_check("Hello %s %u", "World", 2.0); // shows static_assert() argument error for argument 2 (float, not unsigned integral)
61 * jau_string_checkLine("Hello %s %u", "World", 2.0); // shows static_assert() source-line error for argument 2 (float, not unsigned integral)
62 * ```
63 * - Runtime safe string formatting via `jau::format_string`
64 * ```
65 * std::string s0 = "World";
66 * std::string s1 = jau::format_string("Hello %s, %d + %d = %'d", s0, 1, 1, 2000);
67 * std::string s3 = jau::format_string_h(100, "Hello %s, %d + %d = %'d", s0, 1, 1, 2000); // using a string w/ reserved size of 100
68 *
69 * // string concatenation, each `formatR` appends to the given referenced string
70 * std::string concat;
71 * concat.reserve(1000);
72 * jau::cfmt::formatR(concat, "Hello %s, %d + %d = %'d", s0, 1, 1, 2000);
73 * ...
74 * jau::cfmt::formatR(concat, "%#b", 2U);
75 * ```
76 * - Both, compile time check and runtime formatting via `jau_format_string` macro (the actual goal)
77 * ```
78 * std::string s1 = jau_format_string("Hello %s, %d + %d = %'d", s0, 1, 1, 2000);
79 * std::string s2 = jau_format_string_h(100, "Hello %s, %d + %d = %'d", s0, 1, 1, 2000); // using a string w/ reserved size of 100
80 * ```
81 * - Compatible with [fprintf](https://en.cppreference.com/w/cpp/io/c/fprintf), [snprintf](https://www.man7.org/linux/man-pages/man3/snprintf.3p.html), etc
82 *
83 * ### Type Conversion
84 * Implementation follows type conversion rules as described
85 * in [Variadic Default Conversion](https://en.cppreference.com/w/cpp/language/variadic_arguments#Default_conversions)
86 * - float to double promotion
87 * - bool, char, short, and unscoped enumerations are converted to int or wider integer types,
88 * see also [va_arg](https://en.cppreference.com/w/cpp/utility/variadic/va_arg)
89 * - void pointer tolerance
90 * - Exception signedness conversion
91 * - Allows positive signed to unsigned type conversion
92 * - Positive check at runtime only
93 * - Allows unsigned to signed type conversion if sizeof(unsigned type) < sizeof(signed type)
94 * - Compile time check
95 * - Otherwise fails intentionally
96 *
97 * ### Implementation Details
98 * #### General
99 * - Validates argument types against format string at compile time (consteval)
100 * - Formats resulting string using argument values against format string at runtime
101 * - Written in C++20 using template argument pack w/ save argument type checks
102 * - Type erasure to wider common denominator, i.e. `uint64`, `const char* const`, `std::string_view`,
103 * reducing code footprint
104 *
105 * #### Behavior
106 * - `nullptr` conversion value similar to `glibc`
107 * - string produces `(null)`
108 * - pointer produces `(nil)`
109 * - Safe Signedness Conversion
110 * - Not accepting `unsigned` -> `signed` conversion if sizeof(unsigned type) >= sizeof(signed type)
111 * to avoid overflow (compile time check), otherwise OK
112 * - Not accepting negative integral value for `unsigned` (runtime check)
113 * - Accept given type <= integral target type, conversion to wider types
114 * - Accept `enum` types for integer conversion.
115 * - Only if underlying type is `unsigned`, it can't be used for signed integer conversion (see above)
116 * - Accept direct std::string and std::string_view for `%s` string arguments
117 * - Arithmetic integral + floating point types are limited to a maximum of 64-bit
118 * - Runtime Errors
119 * - Failed runtime checks will inject an error market,
120 * e.g. `<E#1@1234:Cnv>` where the 1st argument caused a conversion error (`Cnv`)
121 * as detected in `string_cfmt.hpp` like 1234.
122 * - Argument 0 indicated an error in the format string.
123 * - `Len` tags a length modifier error
124 * - `Cnv` tags a conversion error
125 *
126 * ### Supported Format String
127 *
128 * `%[flags][width][.precision][length modifier]conversion`
129 *
130 * ### Flags
131 * The following flags are supported
132 * - `#`: hash, C99. Adds leading prefix for `radix != 10`.
133 * - `0`: zeropad, C99
134 * - `-`: left, C99
135 * - <code>&nbsp;</code>: space, C99
136 * - `+`: plus, C99
137 * - ``'``: thousands, POSIX
138 * - `,`: thousands, OpenJDK (alias for Java users)
139 *
140 * #### Width and Precision
141 * Width and precision also supports `*` to use the next argument for its value.
142 *
143 * However, `*m$` (decimal integer `m`) for the `m`-th argument is not yet supported.
144 *
145 * #### Length Modifiers
146 * The following length modifiers are supported
147 * - `hh` [unsigned] char, ignored for floating point
148 * - `h` [unsigned] short, ignored for floating point
149 * - `l` [unsigned] long, ignored for floating point
150 * - `ll` [unsigned] long long
151 * - `q` deprecated synonym for `ll`
152 * - `L` long double
153 * - `j` uintmax_t or intmax_t
154 * - `z` size_t or ssize_t
155 * - `Z` deprecated synonym for `z`
156 * - `t` ptrdiff_t
157 *
158 * #### Conversion Specifiers
159 * The following standard conversion specifiers are supported:
160 * - Basic
161 * - `c` character
162 * - `s` string
163 * - `p` pointer
164 * - `d` signed integral or `i`
165 * - Unsigned integral
166 * - `o` octal unsigned
167 * - `x` `X` hexadecimal unsigned low and capital chars
168 * - `b` binary unsigned presentation (extension)
169 * - `u` decimal unsigned
170 * - Floating point
171 * - `f` or `F` double floating point
172 * - `e`, `E` exponential low- and capital E
173 * - `g`, `G` alternate exponential low- and capital E
174 * - `a`, `A` hexadecimal low- and capital chars
175 * - Aliases
176 * - `i` -> `d`
177 * - `F` -> `f`
178 *
179 * #### Extended Conversion Specifier
180 * - `b` bitpattern of unsigned integral w/ prefix `0b` (if `#` flag is added)
181 *
182 * ### Special Thanks
183 * To the project [A printf / sprintf Implementation for Embedded Systems](https://github.com/mpaland/printf)
184 * worked on by Marco Paland and many others. I have used their `test_suite.cpp` code within our unit test
185 * `test_stringfmt_format.cpp` and also utilized their floating point parsing within
186 * `append_float` and `append_efloat`.
187 *
188 * ### Further Documentation
189 * - [C++ fprintf Reference](https://en.cppreference.com/w/cpp/io/c/fprintf)
190 * - [Linux snprintf(3) man page](https://www.man7.org/linux/man-pages/man3/snprintf.3p.html)
191 * - [FreeBSD snprintf(3) man page](https://man.freebsd.org/cgi/man.cgi?snprintf(3))
192 * - [C++20 idioms for parameter packs](https://www.scs.stanford.edu/~dm/blog/param-pack.html)
193 * - [A printf / sprintf Implementation for Embedded Systems](https://github.com/mpaland/printf)
194 */
195namespace jau::cfmt {
196 using namespace jau::enums;
197
198 /** \addtogroup StringUtils
199 *
200 * @{
201 */
202
203 /// Maximum net number string len w/o EOS, up to uint64_t
204 constexpr inline const size_t num_max_slen = 31;
205
206 /// Default string reserved capacity w/o EOS (511)
207 constexpr inline const size_t default_string_capacity = 511;
208
216 constexpr static const char *to_string(pstate_t s) noexcept {
217 switch( s ) {
218 case pstate_t::outside: return "outside";
219 case pstate_t::start: return "start";
220 case pstate_t::field_width: return "width";
221 case pstate_t::precision: return "precision";
222 default: return "error";
223 }
224 }
225
226 /// Format flags
227 enum class flags_t : uint16_t {
228 none = 0, ///< no flag
229
230 hash = (uint16_t)1 << 1, ///< actual flag `#`, C99
231 zeropad = (uint16_t)1 << 2, ///< actual flag `0`, C99
232 left = (uint16_t)1 << 3, ///< actual flag `-`, C99
233 space = (uint16_t)1 << 4, ///< actual flag ` `, C99
234 plus = (uint16_t)1 << 5, ///< actual flag `+`, C99
235 thousands = (uint16_t)1 << 6, ///< actual flag `\'`, POSIX
236
237 uppercase = (uint16_t)1 << 8 ///< uppercase, via conversion spec
238 };
240
241 /// Format length modifiers
242 enum class plength_t : uint16_t {
244 hh, ///< char integer
245 h, ///< short integer
246 l, ///< long integer
247 ll, ///< long long integer
248 L, ///< long double float
249 j, ///< intmax_t or uintmax_t integer
250 z, ///< size_t or ssize_t integer
251 t ///< ptrdiff_t
252 };
254
255 /// Format conversion specifier (fully defined w/ radix)
256 enum class cspec_t : uint16_t {
257 none, ///< none
258 character, ///< `c`
259 string, ///< `s`
260 pointer, ///< `p`
261 signed_int, ///< `d` or `i`
262 unsigned_int, ///< `o`, `x` or `X`, `u`, `b`
263 floating_point, ///< `f` or `F`
264 exp_float, ///< `e` or `E`
265 alt_float, ///< `g` or `G`
266 hex_float, ///< `a` or `A`
267 };
270
271 struct FormatOpts {
272 std::string_view fmt;
273 size_t width;
274 size_t precision;
279 bool width_set:1;
281
282 constexpr FormatOpts() noexcept
283 : fmt(), width(0), precision(0), radix(0),
287 width_set(false), precision_set(false)
288 { }
289
290 constexpr void setWidth(size_t v) { width = v; width_set = true; }
291 constexpr void setPrecision(size_t v) { precision = v; precision_set = true; }
292 constexpr bool addFlag(char c) noexcept {
293 switch( c ) {
294 case '#': flags |= flags_t::hash; break;
295 case '0': flags |= flags_t::zeropad; break;
296 case '-': flags |= flags_t::left; break;
297 case ' ': flags |= flags_t::space; break;
298 case '+': flags |= flags_t::plus; break;
299 case '\'': flags |= flags_t::thousands; break;
300 case ',': flags |= flags_t::thousands; break;
301 default: return false; // done, not a flag
302 }
303 return true; // a flag
304 }
305 constexpr void validateFlags() noexcept {
306 switch (conversion) {
308 // plus or space flag only for signed conversions
310 [[fallthrough]];
312 if (precision_set) {
314 }
315 break;
316 default:
317 break;
318 } // switch( fmt_literal )
319 if (is_set(flags, flags_t::left)) {
321 }
322 if (is_set(flags, flags_t::plus)) {
324 }
325 if( 10 == radix ) {
327 }
328 }
329
330 constexpr bool setConversion(char fmt_literal) noexcept {
331 radix = 10; // default
332 switch (fmt_literal) {
333 case 'c':
335 break;
336 case 's':
338 break;
339 case 'p':
340 radix = 16;
343 break;
344
345 case 'd':
346 case 'i':
348 break;
349
350 case 'o':
351 radix = 8;
353 break;
354
355 case 'X':
357 [[fallthrough]];
358 case 'x':
359 radix = 16;
361 break;
362
363 case 'u':
365 break;
366 case 'b':
367 radix = 2;
369 break;
370
371 case 'F':
373 [[fallthrough]];
374 case 'f':
376 break;
377
378 case 'E':
380 [[fallthrough]];
381 case 'e':
383 break;
384
385 case 'G':
387 [[fallthrough]];
388 case 'g':
390 break;
391
392 case 'A':
394 [[fallthrough]];
395 case 'a':
397 break;
398
399 default:
400 return false;
401 } // switch( fmt_literal )
403 return true;
404 }
405
406 /// Reconstructs format string
407 std::string toFormat() const {
408 std::string s;
409 s.reserve(31);
410 s.append("%");
411 if( is_set(flags, flags_t::hash) ) { s.append("#"); }
412 if( is_set(flags, flags_t::zeropad) ) { s.append("0"); }
413 if( is_set(flags, flags_t::left) ) { s.append("-"); }
414 if( is_set(flags, flags_t::space) ) { s.append(" "); }
415 if( is_set(flags, flags_t::plus) ) { s.append("+"); }
416 if( width_set ) {
417 s.append(std::to_string(width));
418 }
419 if( precision_set) {
420 s.append(".").append(std::to_string(precision));
421 }
422 if( plength_t::none != length_mod ) {
423 s.append(to_string(length_mod));
424 }
425 switch( conversion ) {
427 s.append("c");
428 break;
429 case cspec_t::string:
430 s.append("s");
431 break;
432 case cspec_t::pointer:
433 s.append("p");
434 break;
436 s.append("d");
437 break;
439 if( 16 == radix ) {
440 s.append( is_set(flags, flags_t::uppercase) ? "X" : "x" );
441 } else if( 8 == radix ) {
442 s.append("o");
443 } else if( 2 == radix ) {
444 s.append("b");
445 } else {
446 s.append("u");
447 }
448 } break;
450 s.append( is_set(flags, flags_t::uppercase) ? "F" : "f" );
451 break;
453 s.append( is_set(flags, flags_t::uppercase) ? "E" : "e" );
454 break;
456 s.append( is_set(flags, flags_t::uppercase) ? "A" : "a" );
457 break;
459 s.append( is_set(flags, flags_t::uppercase) ? "G" : "g" );
460 break;
461 default:
462 s.append("E");
463 break;
464 } // switch( fmt_literal )
465 return s;
466 }
467
468 constexpr void reset() noexcept {
469 fmt = std::string_view();
470 width = 0;
471 precision = 0;
472 radix = 0;
476 width_set = false;
477 precision_set = false;
478 }
479
480 std::string toString() const {
481 std::string s = "fmt `";
482 s.append(fmt).append("` -> `").append(toFormat())
483 .append("`, flags ")
484 .append(to_string(flags))
485 .append(", width ");
486 if( width_set ) {
487 s.append(std::to_string(width));
488 } else {
489 s.append("no");
490 }
491 s.append(", precision ");
492 if( precision_set ) {
493 s.append(std::to_string(precision));
494 } else {
495 s.append("no");
496 }
497 s.append(", length `")
498 .append(to_string(length_mod))
499 .append("`, cspec ").append(to_string(conversion))
500 .append(", radix ").append(std::to_string(radix));
501 return s;
502 }
503 };
504
505 class Result {
506 private:
507 std::string_view m_fmt;
508 size_t m_pos; ///< position of next fmt character to be read
509 ssize_t m_arg_count;
510 int m_line;
511 FormatOpts m_opts;
512 bool m_success:1; ///< true if operation was successful, otherwise indicates error
513
514 public:
515 constexpr Result(std::string_view f, FormatOpts o, size_t pos, ssize_t acount, int line, bool ok)
516 : m_fmt(f), m_pos(pos), m_arg_count(acount), m_line(line), m_opts(o), m_success(ok) {}
517
518 /// true if operation was successful, otherwise indicates error
519 constexpr bool success() const noexcept { return m_success; }
520
521 /// Arguments processed
522 constexpr ssize_t argumentCount() const noexcept { return m_arg_count; }
523
524 /// format string_view
525 constexpr const std::string_view& fmt() const noexcept { return m_fmt; }
526
527 /// Last argument FormatOpts (error analysis)
528 constexpr const FormatOpts& opts() const noexcept { return m_opts; }
529 /// Position of next fmt character to be read (error analysis)
530 constexpr size_t pos() const noexcept { return m_pos; }
531 /// error line of implementation source code or zero if success (error analysis)
532 constexpr int errorLine() const noexcept { return m_line; }
533
534 std::string toString() const {
535 const char c = m_pos < m_fmt.length() ? m_fmt[m_pos] : '@';
536 std::string s = "args ";
537 s.append(std::to_string(m_arg_count))
538 .append(", ok ")
539 .append(jau::to_string(m_success))
540 .append(", line ")
541 .append(std::to_string(m_line))
542 .append(", pos ")
543 .append(std::to_string(m_pos))
544 .append(", char `")
545 .append(std::string(1, c))
546 .append("`, last[").append(m_opts.toString())
547 .append("], fmt `").append(m_fmt)
548 .append("`");
549 return s;
550 }
551 };
552
553 inline std::ostream &operator<<(std::ostream &out, const Result &pc) {
554 out << pc.toString();
555 return out;
556 }
557
558 namespace impl {
559 inline constexpr const size_t float_charbuf_maxlen = 32;
560 inline constexpr const size_t default_float_precision = 6;
561 inline constexpr const double_t max_append_float = (double_t)1e9;
562
563 void append_rev(std::string &dest, const size_t dest_maxlen, std::string_view src, bool prec_cut, bool reverse, const FormatOpts &opts) noexcept;
564 inline void append_string(std::string &dest, const size_t dest_maxlen, std::string_view src, const FormatOpts &opts) noexcept {
565 append_rev(dest, dest_maxlen, src, true /*prec*/, false /*rev**/, opts);
566 }
567
568 void append_integral(std::string &dest, const size_t dest_maxlen, const uint64_t val, bool negative, const FormatOpts &opts, bool inject_dot=false) noexcept;
569
570 // check for NaN and special values
571 template<std::floating_point value_type>
572 requires (!std::is_same_v<double, value_type>)
573 bool is_float_validT(std::string &dest, const size_t dest_maxlen, const value_type value, const FormatOpts &opts) noexcept {
574 const bool up = is_set(opts.flags, flags_t::uppercase);
575 if (value != value) {
576 append_string(dest, dest_maxlen, up ? "NAN" : "nan", opts);
577 return false;
578 } else if (value < -std::numeric_limits<value_type>::max()) {
579 append_string(dest, dest_maxlen, up ? "-INF" : "-inf", opts);
580 return false;;
581 } else if (value > std::numeric_limits<value_type>::max()) {
582 const bool plus = is_set(opts.flags, flags_t::plus);
583 append_string(dest, dest_maxlen, plus ? ( up ? "+INF" : "+inf" ) : ( up ? "INF" : "inf" ), opts);
584 return false;
585 }
586 return true;
587 }
588 bool is_float_validF64(std::string &dest, const size_t dest_maxlen, const double value, const FormatOpts &opts) noexcept;
589
590 void append_floatF64(std::string &dest, const size_t dest_maxlen, const double value, const FormatOpts &opts) noexcept;
591 void append_efloatF64(std::string &dest, const size_t dest_maxlen, const double ivalue, const FormatOpts &iopts) noexcept;
592 void append_afloatF64(std::string &dest, const size_t dest_maxlen, const double ivalue, const size_t ivalue_size, const FormatOpts &iopts) noexcept;
593
594 template<typename T>
595 concept OutputType = requires(T t) {
596 { t.maxLen() } -> std::same_as<size_t>;
597 // { t.fits(size_t) } -> std::same_as<bool>;
598
599 { t.get() } -> std::same_as<std::string_view>;
600 };
601
602 /// A null OutputType for `constexpr` and `consteval` formatting dropping all output
604 public:
605 constexpr NullOutput() noexcept = default;
606
607 constexpr size_t maxLen() const noexcept { return 0; }
608 constexpr bool fits(size_t) const noexcept { return false; }
609
610 std::string_view get() const noexcept { return "(nil)"; }
611
612 template<typename T>
614 constexpr void appendFormatted(const FormatOpts&, const T&) noexcept { }
615
616 template<typename T>
618 constexpr void appendFormattedInt(const FormatOpts&, const T&, bool) noexcept { }
619
620 template<typename T>
621 requires std::is_floating_point_v<T>
622 constexpr void appendFormattedFloat(const FormatOpts&, const T&, nsize_t) noexcept {}
623
624 constexpr void appendText(std::string_view ) noexcept { }
625 constexpr void appendError(size_t, int , const std::string_view ) noexcept {}
626 };
627
628 /// A std::string OutputType for runtime formatting into a std::string
630 private:
631 std::size_t m_maxLen;
632 std::string &m_s;
633
634 public:
635 StringOutput(std::size_t maxLen, std::string &s) noexcept : m_maxLen(maxLen), m_s(s) {}
636
637 constexpr size_t maxLen() const noexcept { return m_maxLen; }
638 constexpr bool fits(size_t n) const noexcept { return 0 < m_maxLen && n <= m_maxLen - m_s.size(); }
639
640 std::string_view get() const noexcept { return m_s; }
641
642 template<typename T>
644 void appendFormatted(const FormatOpts& opts, const T& v) noexcept {
645 std::exception_ptr eptr;
646 try {
647 impl::append_string(m_s, m_maxLen, v, opts);
648 } catch (...) {
649 eptr = std::current_exception();
650 }
651 handle_exception(eptr);
652 }
653 template<typename T>
655 void appendFormatted(const FormatOpts& opts, const T& v) noexcept {
656 std::exception_ptr eptr;
657 try {
658 if( nullptr != v ) {
659 impl::append_string(m_s, m_maxLen, v, opts);
660 } else {
661 impl::append_string(m_s, m_maxLen, "(null)", opts);
662 }
663 } catch (...) {
664 eptr = std::current_exception();
665 }
666 handle_exception(eptr);
667 }
668 template<typename T>
669 requires jau::req::boolean<T>
670 void appendFormatted(const FormatOpts& opts, const T& v) noexcept {
671 std::exception_ptr eptr;
672 try {
673 impl::append_string(m_s, m_maxLen, jau::to_string(v), opts);
674 } catch (...) {
675 eptr = std::current_exception();
676 }
677 handle_exception(eptr);
678 }
679 template<typename T>
681 void appendFormatted(const FormatOpts& opts, const T& v) noexcept {
682 std::exception_ptr eptr;
683 try {
684 if( nullptr != v ) {
685 const uintptr_t v_le = jau::cpu_to_le(reinterpret_cast<uintptr_t>(v));
686 impl::append_integral(m_s, m_maxLen, v_le, false, opts);
687 } else {
688 impl::append_string(m_s, m_maxLen, "(nil)", opts);
689 }
690 } catch (...) {
691 eptr = std::current_exception();
692 }
693 handle_exception(eptr);
694 }
695 template<typename T>
697 void appendFormattedInt(const FormatOpts& opts, const T& v, bool negative) noexcept {
698 std::exception_ptr eptr;
699 try {
700 impl::append_integral(m_s, m_maxLen, uint64_t(jau::abs(v)), negative, opts);
701 } catch (...) {
702 eptr = std::current_exception();
703 }
704 handle_exception(eptr);
705 }
706 template<typename T>
707 requires std::is_floating_point_v<T>
708 void appendFormattedFloat(const FormatOpts& opts, const T& v, nsize_t floatSize) noexcept {
709 std::exception_ptr eptr;
710 try {
711 if( opts.conversion == cspec_t::floating_point ) {
712 impl::append_floatF64(m_s, m_maxLen, v, opts);
713 } else if( opts.conversion == cspec_t::hex_float ) {
714 impl::append_afloatF64(m_s, m_maxLen, v, floatSize, opts);
715 } else {
716 // cspec_t::exp_float, cspec_t::alt_float
717 impl::append_efloatF64(m_s, m_maxLen, v, opts);
718 }
719 } catch (...) {
720 eptr = std::current_exception();
721 }
722 handle_exception(eptr);
723 }
724 void appendText(const std::string_view v) noexcept {
725 if( fits(v.size()) ) {
726 std::exception_ptr eptr;
727 try {
728 m_s.append(v);
729 } catch (...) {
730 eptr = std::current_exception();
731 }
732 handle_exception(eptr);
733 }
734 }
735 void appendError(size_t argIdx, int line, const std::string_view tag) noexcept {
736 std::exception_ptr eptr;
737 try {
738 m_s.append("<E#").append(std::to_string(argIdx)).append("@").append(std::to_string(line)).append(":").append(tag).append(">");
739 } catch (...) {
740 eptr = std::current_exception();
741 }
742 handle_exception(eptr);
743 }
744 };
745
746 template<OutputType Output>
747 class Parser; // fwd
748
749 class no_type_t {};
750
751 template<OutputType Output>
752 class FResult {
753 public:
754 std::string_view fmt;
755 size_t pos; ///< position of next fmt character to be read
756 ssize_t arg_count;
757 int line;
760
761 private:
762 Output m_out;
763 size_t pos_lstart; ///< start of last conversion spec
764 unsigned int m_argtype_size;
765 bool m_argtype_signed:1;
766 bool m_argval_negative:1;
767
768 public:
769 constexpr FResult(Output p, std::string_view fmt_) noexcept
770 : fmt(fmt_), pos(0), arg_count(0), line(0), opts(),
772 m_out(std::move(p)), pos_lstart(0),
773 m_argtype_size(0),
774 m_argtype_signed(false),
775 m_argval_negative(false)
776 { }
777
778 constexpr FResult(const FResult &pre) noexcept = default;
779 constexpr FResult &operator=(const FResult &x) noexcept = default;
780
781 constexpr operator Result() const noexcept {
783 }
784
785 constexpr bool hasNext() const noexcept {
786 return !error() && pos < fmt.length();
787 }
788
789 constexpr ssize_t argCount() const noexcept { return arg_count; }
790 constexpr bool error() const noexcept { return pstate_t::error == state; }
791
792 std::string toString() const {
793 const char c = pos < fmt.length() ? fmt[pos] : '@';
794 std::string s = "args ";
795 s.append(std::to_string(arg_count))
796 .append(", state ")
797 .append(to_string(state))
798 .append(", line ")
799 .append(std::to_string(line))
800 .append(", pos ")
801 .append(std::to_string(pos))
802 .append(", char `")
803 .append(std::string(1, c))
804 .append("`, last[")
805 .append(opts.toString())
806 .append("`, type[signed ")
807 .append(jau::to_string(m_argtype_signed))
808 .append(", size ")
809 .append(std::to_string(m_argtype_size))
810 .append("], negative ")
811 .append(jau::to_string(m_argval_negative))
812 .append("], fmt `")
813 .append(fmt)
814 .append("`, out `")
815 .append(m_out.get())
816 .append("`");
817 return s;
818 }
819
820 private:
821 friend class impl::Parser<Output>;
822
823 constexpr void reset() noexcept {
824 opts.reset();
825 }
826 constexpr bool nextSymbol(char &c) noexcept {
827 if (pos < fmt.length()) {
828 c = fmt[pos++];
829 return true;
830 } else {
831 return false;
832 }
833 }
834 constexpr bool toConversion() noexcept {
835 if (pstate_t::outside != state) {
836 return true; // inside conversion specifier
837 } else if (fmt[pos] == '%') {
838 state = pstate_t::start; // just at start of conversion specifier
839 pos_lstart = pos++;
840 reset();
841 return true;
842 } else if (pos < fmt.length()) {
843 // seek next conversion specifier
844 const size_t q = fmt.find('%', pos + 1);
845 if (q == std::string::npos) {
846 // no conversion specifier found
847 appendText(fmt.substr(pos, fmt.length() - pos));
848 pos = fmt.length();
849 return false;
850 } else {
851 // new conversion specifier found
852 appendText(fmt.substr(pos, q - pos));
854 pos_lstart = pos;
855 pos = q + 1;
856 reset();
857 return true;
858 }
859 } else {
860 // end of format
861 return false;
862 }
863 }
864
865 constexpr void setLastSpec(size_t endpos) noexcept {
866 if (endpos > pos_lstart) {
867 opts.fmt = fmt.substr(pos_lstart, endpos - pos_lstart);
868 }
869 }
870
871 constexpr void setError(int l) noexcept {
872 line = l;
874 if (0 == arg_count) {
875 arg_count = std::numeric_limits<ssize_t>::min();
876 } else if (0 < arg_count) {
877 arg_count *= -1;
878 }
879 }
880
881 template<typename T>
882 requires jau::req::stringifyable_jau<T> && (!jau::req::unsigned_integral<T>) && (!std::floating_point<T>)
883 constexpr FResult &appendFormatted(const T &v) noexcept {
884 m_out.appendFormatted(opts, v);
885 return *this;
886 }
887 template<typename T>
888 requires jau::req::unsigned_integral<T> && (!jau::req::boolean<T>)
889 constexpr FResult &appendFormatted(const T &v) noexcept {
890 m_out.appendFormattedInt(opts, v, m_argval_negative);
891 return *this;
892 }
893
894 template<typename T>
895 requires std::floating_point<T>
896 constexpr FResult &appendFormatted(const T &v) noexcept {
897 m_out.appendFormattedFloat(opts, v, m_argtype_size);
898 return *this;
899 }
900
901 constexpr FResult &appendText(const std::string_view v) noexcept {
902 m_out.appendText(v);
903 return *this;
904 }
905 constexpr FResult &appendError(const std::string_view tag) noexcept {
906 const ssize_t c = arg_count == std::numeric_limits<ssize_t>::min() ? 0 : arg_count;
907 m_out.appendError(jau::abs(c), line, tag);
908 return *this;
909 }
910 };
911
912 // A NullOutput formatting result, capable of `constexpr` and `consteval`
914
915 template <jau::req::signed_integral T>
916 constexpr T abs_int(const T x) noexcept
917 {
918 return jau::sign<T>(x) < 0 ? jau::invert_sign<T>( x ) : x;
919 }
920
921 template<typename T>
923 constexpr T abs_int(const T& x) noexcept {
924 return x;
925 }
926
927 template<typename T>
929 constexpr bool is_positive(const T a) noexcept {
930 return a >= 0;
931 }
932
933 template<typename T>
934 requires std::floating_point<T>
935 constexpr bool is_positive(const T a) noexcept {
936 return a >= 0;
937 }
938
939 template<typename T>
940 requires (!jau::req::signed_integral<T>) && (!std::floating_point<T>)
941 constexpr bool is_positive(const T&) noexcept {
942 return true;
943 }
944
945 /// Returns uint64_t type if: integral || boolean, otherwise returns orig type
946 template<typename T>
947 using make_int_unsigned_t = typename std::conditional_t<std::is_integral_v<T> || jau::req::boolean<T>, std::type_identity<uint64_t>, std::type_identity<T>>::type; // NOLINT
948
949 /// Returns signed-type variation if: unsigned-integral && !boolean, otherwise returns orig type
950 template<typename T>
951 using make_int_signed_t = typename std::conditional_t<jau::req::unsigned_integral<T> && !jau::req::boolean<T>, std::make_signed<T>, std::type_identity<T>>::type; // NOLINT
952
953 /// Returns a simple `const char * const` if: pointer, otherwise returns orig type
954 template<typename T>
955 using make_simple_pointer_t = typename std::conditional_t<jau::req::pointer<T>, std::type_identity<const char * const>, std::type_identity<T>>::type; // NOLINT
956
957 /// A StringOutput formatting result for runtime formatting into a std::string
959 template<OutputType Output>
960 class Parser {
961 public:
963 constexpr Parser() noexcept = default;
964
965 template <typename T>
966 requires std::is_integral_v<T>
967 constexpr void parseOne(Result &pc, const T &val) const noexcept {
968 pc.m_argtype_size = sizeof(T);
969 pc.m_argtype_signed = std::is_signed_v<T>;
970 pc.m_argval_negative = !is_positive(val);
971 using U = make_int_unsigned_t<T>;
972 parseOneImpl<U>(pc, U(abs_int(val))); // uint64_t
973 }
974
975 template <typename T>
976 requires std::is_floating_point_v<T>
977 constexpr void parseOne(Result &pc, const T &val) const noexcept {
978 pc.m_argtype_size = sizeof(T);
979 pc.m_argtype_signed = true;
980 pc.m_argval_negative = !is_positive(val);
981 parseOneImpl<double>(pc, double(val)); // double
982 }
983
984 template <typename T>
985 requires jau::req::pointer<T> // also allows passing `char*` for `%p`
986 constexpr void parseOne(Result &pc, const T &val) const noexcept {
987 pc.m_argtype_size = sizeof(T); // NOLINT(bugprone-sizeof-expression)
988 pc.m_argtype_signed = false;
989 pc.m_argval_negative = false;
990 using U = make_simple_pointer_t<T>; // aliasing to 'char*'
991 parseOneImpl<U>(pc, U(val)); // pass-through
992 }
993
994 template <typename T>
996 constexpr void parseOne(Result &pc, const T &val) const noexcept {
997 pc.m_argtype_size = sizeof(T); // NOLINT(bugprone-sizeof-expression)
998 pc.m_argtype_signed = false;
999 pc.m_argval_negative = false;
1000 parseOneImpl<std::string_view>(pc, std::string_view(val)); // pass as string_view
1001 }
1002
1003 template <typename T>
1004 requires (!(std::is_integral_v<T> || std::is_floating_point_v<T> || jau::req::pointer<T> || jau::req::string_alike<T>)) // not: jau::req::string_literal<T> || jau::req::string_class<T> || jau::req::char_pointer<T>
1005 constexpr void parseOne(Result &pc, const T &val) const noexcept {
1006 pc.m_argtype_size = sizeof(T); // NOLINT(bugprone-sizeof-expression)
1007 pc.m_argtype_signed = std::is_signed_v<T>;
1008 pc.m_argval_negative = !is_positive(val);
1009 parseOneImpl<T>(pc, val); // pass-through
1010 }
1011
1012 template <typename T>
1013 requires std::is_integral_v<T>
1014 constexpr void checkOne(Result &pc) const noexcept {
1015 pc.m_argtype_size = sizeof(T);
1016 pc.m_argtype_signed = std::is_signed_v<T>;
1017 pc.m_argval_negative = false;
1018 using U = make_int_unsigned_t<T>;
1019 parseOneImpl<U>(pc, U()); // uint64_t
1020 }
1021
1022 template <typename T>
1023 requires std::is_floating_point_v<T>
1024 constexpr void checkOne(Result &pc) const noexcept {
1025 pc.m_argtype_size = sizeof(T);
1026 pc.m_argtype_signed = true;
1027 pc.m_argval_negative = false;
1028 parseOneImpl<double>(pc, double()); // double
1029 }
1030
1031 template <typename T>
1032 requires jau::req::pointer<T> // also allows passing `char*` for `%p`
1033 constexpr void checkOne(Result &pc) const noexcept {
1034 pc.m_argtype_size = sizeof(T); // NOLINT(bugprone-sizeof-expression)
1035 pc.m_argtype_signed = false;
1036 pc.m_argval_negative = false;
1038 parseOneImpl<U>(pc, U()); // pass-through
1039 }
1040
1041 template <typename T>
1043 constexpr void checkOne(Result &pc) const noexcept {
1044 pc.m_argtype_size = sizeof(T); // NOLINT(bugprone-sizeof-expression)
1045 pc.m_argtype_signed = false;
1046 pc.m_argval_negative = false;
1047 parseOneImpl<std::string_view>(pc, std::string_view()); // pass as string_view
1048 }
1049
1050 template <typename T>
1051 requires (!(std::is_integral_v<T> || std::is_floating_point_v<T> || jau::req::pointer<T> || jau::req::string_alike<T>)) // not: jau::req::string_literal<T> || jau::req::string_class<T> || jau::req::char_pointer<T>
1052 constexpr void checkOne(Result &pc) const noexcept {
1053 pc.m_argtype_size = sizeof(T); // NOLINT(bugprone-sizeof-expression)
1054 pc.m_argtype_signed = std::is_signed_v<T>;
1055 pc.m_argval_negative = false;
1056 parseOneImpl<T>(pc, T()); // pass-through
1057 }
1058
1059 private:
1060 /**
1061 * Parse the given argument against the current conversion specifier of the format string.
1062 *
1063 * Multiple rounds of parsing calls might be required, each passing the next argument or null.
1064 *
1065 * Parsing is completed when method returns false, either signaling an error() or completion.
1066 *
1067 * Caller only checks error status in the end, since all arguments will be processed due to folding parameter pack
1068 *
1069 * Hidden in private to only access via public constrained template buddies!
1070 *
1071 * @tparam T The type of the given argument
1072 * @return true if no error _and_ not complete, i.e. further calls with subsequent parameter required. Otherwise parsing ended due to error or completeness.
1073 */
1074 template <typename T>
1075 constexpr void parseOneImpl(Result &pc, const T &val) const noexcept {
1076 if( !pc.hasNext() ) {
1077 return; // done or error
1078 }
1079
1080 bool loop_next;
1081 char c;
1082 do {
1083 if( !pc.toConversion() ) {
1084 return; // done
1085 }
1086 // pstate_t::outside != _state
1087
1088 /* skip '%' or previous `*` */
1089 if( !pc.nextSymbol(c) ) {
1090 pc.setError(__LINE__);
1091 return; // error
1092 }
1093
1094 if( pstate_t::start == pc.state ) {
1096 parseFlags(pc, c);
1097
1098 /* parse field width */
1099 if( c == '*' ) {
1100 // error or continue with next argument for same conversion -> field_width
1101 parseArgWidthPrecision<T>(true, pc, val);
1102 return;
1103 } else {
1104 if( !parseFmtWidthPrecision(true, pc, c) ) {
1105 if( pc.error() ) {
1106 return;
1107 }
1108 // no width, continue with same argument for same conversion -> field_width
1109 }
1110 }
1111 }
1112
1113 if( pstate_t::field_width == pc.state ) {
1114 /* parse precision */
1116 if( c == '.' ) {
1117 if( !pc.nextSymbol(c) ) {
1118 pc.setError(__LINE__); // missing number + spec
1119 return; // error
1120 }
1121 if( c == '*' ) {
1122 // error or continue with next argument for same conversion -> field_width
1123 parseArgWidthPrecision<T>(false, pc, val);
1124 return;
1125 } else {
1126 if( !parseFmtWidthPrecision(false, pc, c) ) {
1127 if( pc.error() ) {
1128 return;
1129 }
1130 // no explicit precision -> zero precision, continue with same argument
1131 pc.opts.setPrecision(0);
1132 }
1133 }
1134 }
1135 }
1136 if( !parseLengthMods(pc, c) ) {
1137 pc.appendError("Len");
1138 return; // error
1139 }
1140 pc.setLastSpec(pc.pos);
1141
1142 if( c == '%' ) {
1143 loop_next = true;
1144 pc.appendText("%");
1145 } else {
1146 loop_next = false;
1147 if( !parseFmtSpec<T>(pc, c, val) ) {
1148 pc.appendError("Cnv");
1149 return; // error
1150 }
1151 }
1152
1153 // next conversion specifier
1155
1156 } while (loop_next);
1157
1158 // return pc.hasNext(); // true: no-error and not-complete
1159 }
1160
1161 constexpr void parseFlags(Result &pc, char &c) const noexcept {
1162 while( pc.opts.addFlag(c) && pc.nextSymbol(c) ) { }
1163 }
1164
1165 /// Parse argument field width or precision, returns false on error. Otherwise next argument is required.
1166 template <typename T>
1167 requires (!jau::req::unsigned_integral<T>)
1168 constexpr void parseArgWidthPrecision(bool, Result &pc, const T &) const noexcept {
1169 pc.setError(__LINE__);
1170 }
1171 /// Parse argument field width or precision, returns false on error. Otherwise next argument is required.
1172 template <typename T>
1173 requires (jau::req::unsigned_integral<T>)
1174 constexpr void parseArgWidthPrecision(bool is_width, Result &pc, const T &val) const noexcept {
1175 if constexpr( std::is_same_v<no_type_t, T> ) {
1176 pc.setError(__LINE__);
1177 return; // error
1178 }
1179 ++pc.arg_count;
1180 if ( pc.m_argtype_size > sizeof(int) ) { // NOLINT(bugprone-sizeof-expression)
1181 pc.setError(__LINE__);
1182 return; // error
1183 }
1184 if( pc.m_argval_negative ) {
1185 if( is_width ) {
1186 pc.opts.flags |= flags_t::left; // reverse padding
1187 pc.opts.setWidth((size_t)val);
1188 } else {
1189 pc.opts.setPrecision(0);
1190 }
1191 } else {
1192 if( is_width ) {
1193 pc.opts.setWidth((size_t)val);
1194 } else {
1195 pc.opts.setPrecision((size_t)val);
1196 }
1197 }
1198 // next argument is required
1199 }
1200
1201 /// Parse format field width or precision, returns true if field is consumed and parsing can continue
1202 /// or false if field has not been consumed or definite error
1203 constexpr bool parseFmtWidthPrecision(bool is_width, Result &pc, char &c) const noexcept {
1204 char buffer[num_max_slen+1];
1205 char *s = &buffer[0];
1206 const char *s_begin = s;
1207 const char *s_end = s + num_max_slen;
1208 while( jau::is_digit(c) && s < s_end ) {
1209 *s = c; ++s;
1210 if( !pc.nextSymbol(c) ) {
1211 pc.setError(__LINE__); // no digit nor spec
1212 return false;
1213 }
1214 }
1215 if( jau::is_digit(c) ) {
1216 pc.setError(__LINE__); // s >= s_end
1217 return false;
1218 }
1219 std::string_view sv(s_begin, s - s_begin);
1220 int64_t num = 0;
1221 if( sv.empty() ) {
1222 return false; // no digits, may continue
1223 }
1224 if( !from_chars(num, sv) ) {
1225 // pc.setError(__LINE__); // number syntax
1226 return false; // error
1227 }
1228 if( is_width ) {
1229 pc.opts.setWidth((size_t)num);
1230 } else {
1231 pc.opts.setPrecision((size_t)num);
1232 }
1233 return true; // continue with current argument
1234 }
1235
1236 /* parse length modifier, returns true if parsing can continue or false on error. */
1237 constexpr bool parseLengthMods(Result &pc, char &c) const noexcept {
1238 if( 'h' == c ) {
1239 if( !pc.nextSymbol(c) ) { return false; }
1240 if( 'h' == c ) {
1241 if( !pc.nextSymbol(c) ) { return false; }
1242 pc.opts.length_mod = plength_t::hh;
1243 } else {
1244 pc.opts.length_mod = plength_t::h;
1245 }
1246 } else if( 'l' == c ) {
1247 if( !pc.nextSymbol(c) ) { return false; }
1248 if( 'l' == c ) {
1249 if( !pc.nextSymbol(c) ) { return false; }
1250 pc.opts.length_mod = plength_t::ll;
1251 } else {
1252 pc.opts.length_mod = plength_t::l;
1253 }
1254 } else if( 'q' == c ) {
1255 if( !pc.nextSymbol(c) ) { return false; }
1256 pc.opts.length_mod = plength_t::ll;
1257 } else if( 'L' == c ) {
1258 if( !pc.nextSymbol(c) ) { return false; }
1259 pc.opts.length_mod = plength_t::L;
1260 } else if( 'j' == c ) {
1261 if( !pc.nextSymbol(c) ) { return false; }
1262 pc.opts.length_mod = plength_t::j;
1263 } else if( 'z' == c || 'Z' == c ) {
1264 if( !pc.nextSymbol(c) ) { return false; }
1265 pc.opts.length_mod = plength_t::z;
1266 } else if( 't' == c ) {
1267 if( !pc.nextSymbol(c) ) { return false; }
1268 pc.opts.length_mod = plength_t::t;
1269 } else {
1270 pc.opts.length_mod = plength_t::none;
1271 }
1272 return true;
1273 }
1274
1275 template <typename T>
1276 constexpr bool parseFmtSpec(Result &pc, char fmt_literal, const T &val) const noexcept {
1277 if( !pc.opts.setConversion(fmt_literal) ) {
1278 pc.setError(__LINE__);
1279 return false;
1280 }
1281
1282 switch( pc.opts.conversion ) {
1283 case cspec_t::character:
1284 return parseCharFmtSpec<T>(pc, val);
1285 case cspec_t::string:
1286 return parseStringFmtSpec<T>(pc, val);
1287 case cspec_t::pointer:
1288 return parseAPointerFmtSpec<T>(pc, val);
1290 return parseSignedFmtSpec<T>(pc, val);
1292 return parseUnsignedFmtSpec<T>(pc, val);
1294 case cspec_t::exp_float:
1295 case cspec_t::hex_float:
1296 case cspec_t::alt_float:
1297 return parseFloatFmtSpec<T>(pc, fmt_literal, val);
1298 default:
1299 pc.setError(__LINE__);
1300 return false;
1301 } // switch( fmt_literal )
1302 }
1303
1304 template <typename T>
1305 requires (!jau::req::unsigned_integral<T>)
1306 constexpr bool parseCharFmtSpec(Result &pc, const T &) const noexcept
1307 {
1308 if constexpr( !std::is_same_v<no_type_t, T> ) {
1309 ++pc.arg_count;
1310 }
1311 pc.setError(__LINE__);
1312 return false;
1313 }
1314
1315 template <typename T>
1316 requires jau::req::unsigned_integral<T> // && (!jau::req::boolean<T>)
1317 constexpr bool parseCharFmtSpec(Result &pc, const T &val0) const noexcept
1318 {
1319 ++pc.arg_count;
1320
1321 using V = make_int_signed_t<T>; // restore signed type!
1322 const V val = V(val0);
1323 const V sign = pc.m_argval_negative ? -1 : 1;
1324
1325 switch( pc.opts.length_mod ) {
1326 case plength_t::none: {
1327 if ( !pc.m_argtype_signed ||
1328 ( sizeof(char) != pc.m_argtype_size &&
1329 sizeof(int) != pc.m_argtype_size ) ) {
1330 pc.setError(__LINE__);
1331 return false;
1332 }
1333 std::string s(1, (char)(val*sign));
1334 pc.appendFormatted(std::string_view(s));
1335 } break;
1336 case plength_t::l: {
1337 if ( !pc.m_argtype_signed ||
1338 ( sizeof(wchar_t) != pc.m_argtype_size && // NOLINT(misc-redundant-expression)
1339 sizeof(wint_t) != pc.m_argtype_size ) ) {
1340 pc.setError(__LINE__);
1341 return false;
1342 }
1343 std::string s(1, (char)(val*sign));
1344 pc.appendFormatted(std::string_view(s)); // FIXME: Support UTF16? UTF8 default
1345 } break;
1346 default:
1347 pc.setError(__LINE__);
1348 return false;
1349 }
1350 return true;
1351 }
1352
1353 template <typename T>
1354 requires (!jau::req::string_alike<T>)
1355 constexpr bool parseStringFmtSpec(Result &pc, const T &) const noexcept
1356 {
1357 if constexpr( !std::is_same_v<no_type_t, T> ) {
1358 ++pc.arg_count;
1359 }
1360 pc.setError(__LINE__);
1361 return false;
1362 }
1363
1364 template <typename T>
1365 requires jau::req::string_alike<T>
1366 constexpr bool parseStringFmtSpec(Result &pc, const T &val) const noexcept
1367 {
1368 ++pc.arg_count;
1369 switch( pc.opts.length_mod ) {
1370 case plength_t::none:
1371 break;
1372 case plength_t::l:
1373 if constexpr( !(std::is_pointer_v<T> &&
1374 std::is_same_v<wchar_t, std::remove_cv_t<std::remove_pointer_t<T>>>) ) {
1375 pc.setError(__LINE__);
1376 return false;
1377 }
1378 break;
1379 default:
1380 // setError();
1381 pc.setError(__LINE__);
1382 return false;
1383 }
1384 pc.appendFormatted(val);
1385 return true;
1386 }
1387
1388 template <typename T>
1389 requires (!jau::req::pointer<T>)
1390 constexpr bool parseAPointerFmtSpec(Result &pc, const T &) const noexcept {
1391 if constexpr( !std::is_same_v<no_type_t, T> ) {
1392 ++pc.arg_count;
1393 }
1394 pc.setError(__LINE__);
1395 return false;
1396 }
1397 template <typename T>
1398 requires jau::req::pointer<T>
1399 constexpr bool parseAPointerFmtSpec(Result &pc, const T &val) const noexcept {
1400 pc.opts.length_mod = plength_t::none;
1401 ++pc.arg_count;
1402 pc.appendFormatted((void *)const_cast<std::remove_const_t<T>>(val)); // force pointer type
1403 return true;
1404 }
1405
1406 template <typename T>
1407 requires (!(jau::req::unsigned_integral<T> || std::is_enum_v<T>))
1408 constexpr bool parseSignedFmtSpec(Result &pc, const T &) const {
1409 if constexpr( !std::is_same_v<no_type_t, T> ) {
1410 ++pc.arg_count;
1411 }
1412 pc.setError(__LINE__);
1413 return false;
1414 }
1415 template <typename T>
1416 requires std::is_enum_v<T> && (!jau::req::signed_integral<std::underlying_type_t<T>>)
1417 constexpr bool parseSignedFmtSpec(Result &pc, const T &) const {
1418 if constexpr( !std::is_same_v<no_type_t, T> ) {
1419 ++pc.arg_count;
1420 }
1421 pc.setError(__LINE__);
1422 return false;
1423 }
1424 template <typename T>
1425 requires std::is_enum_v<T> && jau::req::signed_integral<std::underlying_type_t<T>>
1426 constexpr bool parseSignedFmtSpec(Result &pc, const T &val0) const {
1427 using U = std::underlying_type_t<T>;
1428 using V = make_int_unsigned_t<U>;
1429 const U u = U(val0);
1430 pc.m_argtype_signed = true;
1431 pc.m_argtype_size = sizeof(U);
1432 pc.m_argval_negative = !is_positive(u);
1433 return parseSignedFmtSpec<V>(pc, V(abs_int(u)));
1434 }
1435 template <typename T>
1436 requires jau::req::unsigned_integral<T>
1437 constexpr bool parseSignedFmtSpec(Result &pc, const T &val) const {
1438 ++pc.arg_count;
1439
1440 // Not accepting unsigned -> signed
1441 // FIXME
1442 const unsigned int signed_argtype_size = pc.m_argtype_signed ? pc.m_argtype_size : pc.m_argtype_size + 1;
1443 #if 0
1444 if( !pc.m_argtype_signed ) {
1445 pc.setError(__LINE__);
1446 return false;
1447 }
1448 #endif
1449
1450 if (jau::is_zero(val)) {
1451 pc.opts.flags &= ~flags_t::hash; // no hash for 0 values
1452 }
1453
1454 // we accept given type <= integral target type
1455
1456 switch( pc.opts.length_mod ) {
1457 case plength_t::hh:
1458 if ( signed_argtype_size > sizeof(char) ) { // NOLINT(bugprone-sizeof-expression)
1459 pc.setError(__LINE__);
1460 return false;
1461 }
1462 break;
1463 case plength_t::h:
1464 if ( signed_argtype_size > sizeof(short) ) { // NOLINT(bugprone-sizeof-expression)
1465 pc.setError(__LINE__);
1466 return false;
1467 }
1468 break;
1469 case plength_t::none:
1470 if ( signed_argtype_size > sizeof(int) ) { // NOLINT(bugprone-sizeof-expression)
1471 pc.setError(__LINE__);
1472 return false;
1473 }
1474 break;
1475 case plength_t::l:
1476 if ( signed_argtype_size > sizeof(long) ) { // NOLINT(bugprone-sizeof-expression)
1477 pc.setError(__LINE__);
1478 return false;
1479 }
1480 break;
1481 case plength_t::ll:
1482 if ( signed_argtype_size > sizeof(long long) ) { // NOLINT(bugprone-sizeof-expression)
1483 pc.setError(__LINE__);
1484 return false;
1485 }
1486 break;
1487 case plength_t::j:
1488 if ( signed_argtype_size > sizeof(intmax_t) ) { // NOLINT(bugprone-sizeof-expression)
1489 pc.setError(__LINE__);
1490 return false;
1491 }
1492 break;
1493 case plength_t::z:
1494 if ( signed_argtype_size > sizeof(ssize_t) ) { // NOLINT(bugprone-sizeof-expression)
1495 pc.setError(__LINE__);
1496 return false;
1497 }
1498 break;
1499 case plength_t::t:
1500 if ( signed_argtype_size > sizeof(ptrdiff_t) ) { // NOLINT(bugprone-sizeof-expression)
1501 pc.setError(__LINE__);
1502 return false;
1503 }
1504 break;
1505 default:
1506 pc.setError(__LINE__);
1507 return false;
1508 }
1509 pc.appendFormatted(val);
1510 return true;
1511 }
1512
1513 template <typename T>
1514 requires (!(jau::req::unsigned_integral<T> || std::is_enum_v<T>))
1515 constexpr bool parseUnsignedFmtSpec(Result &pc, const T &) const noexcept {
1516 if constexpr( !std::is_same_v<no_type_t, T> ) {
1517 ++pc.arg_count;
1518 }
1519 pc.setError(__LINE__);
1520 return false;
1521 }
1522 template <typename T>
1523 requires std::is_enum_v<T>
1524 constexpr bool parseUnsignedFmtSpec(Result &pc, const T &val0) const {
1525 using U = std::underlying_type_t<T>;
1526 using V = make_int_unsigned_t<U>;
1527 const U u = U(val0);
1528 pc.m_argtype_signed = std::is_signed_v<U>;
1529 pc.m_argtype_size = sizeof(U);
1530 pc.m_argval_negative = !is_positive(u);
1531 return parseUnsignedFmtSpec<V>(pc, V(abs_int(u)));
1532 }
1533 template <typename T>
1534 requires jau::req::unsigned_integral<T>
1535 constexpr bool parseUnsignedFmtSpec(Result &pc, const T &val) const noexcept {
1536 ++pc.arg_count;
1537
1538 // Accepting signed, but not negative
1539 if( pc.m_argval_negative ) {
1540 pc.setError(__LINE__);
1541 return false;
1542 }
1543
1544 if (jau::is_zero(val)) {
1545 pc.opts.flags &= ~flags_t::hash; // no hash for 0 values
1546 }
1547
1548 // we accept given type <= integral target type
1549
1550 switch( pc.opts.length_mod ) {
1551 case plength_t::hh:
1552 if ( pc.m_argtype_size > sizeof(unsigned char) ) { // NOLINT(bugprone-sizeof-expression)
1553 pc.setError(__LINE__);
1554 return false;
1555 }
1556 break;
1557 case plength_t::h:
1558 if ( pc.m_argtype_size > sizeof(unsigned short) ) { // NOLINT(bugprone-sizeof-expression)
1559 pc.setError(__LINE__);
1560 return false;
1561 }
1562 break;
1563 case plength_t::none:
1564 if ( pc.m_argtype_size > sizeof(unsigned int) ) { // NOLINT(bugprone-sizeof-expression)
1565 pc.setError(__LINE__);
1566 return false;
1567 }
1568 break;
1569 case plength_t::l:
1570 if ( pc.m_argtype_size > sizeof(unsigned long) ) { // NOLINT(bugprone-sizeof-expression)
1571 pc.setError(__LINE__);
1572 return false;
1573 }
1574 break;
1575 case plength_t::ll:
1576 if ( pc.m_argtype_size > sizeof(unsigned long long) ) { // NOLINT(bugprone-sizeof-expression)
1577 pc.setError(__LINE__);
1578 return false;
1579 }
1580 break;
1581 case plength_t::j:
1582 if ( pc.m_argtype_size > sizeof(uintmax_t) ) { // NOLINT(bugprone-sizeof-expression)
1583 pc.setError(__LINE__);
1584 return false;
1585 }
1586 break;
1587 case plength_t::z:
1588 if ( pc.m_argtype_size > sizeof(size_t) ) { // NOLINT(bugprone-sizeof-expression)
1589 pc.setError(__LINE__);
1590 return false;
1591 }
1592 break;
1593
1594 case plength_t::t:
1595 if ( pc.m_argtype_size > sizeof(ptrdiff_t) ) { // NOLINT(bugprone-sizeof-expression)
1596 pc.setError(__LINE__);
1597 return false;
1598 }
1599 break;
1600
1601 default:
1602 pc.setError(__LINE__);
1603 return false;
1604 }
1605 pc.appendFormatted(val);
1606 return true;
1607 }
1608
1609 template <typename T>
1610 requires (!std::floating_point<T>)
1611 constexpr bool parseFloatFmtSpec(Result &pc, const char /*fmt_literal*/, const T &) const noexcept {
1612 if constexpr( !std::is_same_v<no_type_t, T> ) {
1613 ++pc.arg_count;
1614 }
1615 pc.setError(__LINE__);
1616 return false;
1617 }
1618 template <typename T>
1619 requires std::floating_point<T>
1620 constexpr bool parseFloatFmtSpec(Result &pc, const char /*fmt_literal*/, const T &val) const noexcept {
1621 ++pc.arg_count;
1622
1623 using U = std::remove_cv_t<T>;
1624
1625 switch( pc.opts.length_mod ) {
1626 case plength_t::none:
1627 case plength_t::l:
1628 if constexpr( !std::is_same_v<float, U> &&
1629 !std::is_same_v<double, U> ) {
1630 pc.setError(__LINE__);
1631 return false;
1632 }
1633 break;
1634 case plength_t::L:
1635 if constexpr( !std::is_same_v<float, U> &&
1636 !std::is_same_v<double, U> &&
1637 !std::is_same_v<long double, U> ) {
1638 } else {
1639 pc.setError(__LINE__);
1640 return false;
1641 }
1642 break;
1643 default:
1644 pc.setError(__LINE__);
1645 return false;
1646 }
1647 pc.appendFormatted(val);
1648 return true;
1649 }
1650 };
1651
1654
1655 } // namespace impl
1656
1657 /**
1658 * Strict format with type validation of arguments against the format string.
1659 *
1660 * See @ref jau_cfmt_header for details
1661 *
1662 * @tparam Targs the argument template type pack to be validated against the format string
1663 * @param fmt the snprintf format string
1664 * @param args passed arguments, used for template type deduction only
1665 * @return true if successfully parsed format and arguments, false otherwise.
1666 * @see @ref jau_cfmt_header
1667 */
1668 template <typename... Targs>
1669 inline std::string format(std::string_view fmt, const Targs &...args) noexcept {
1670 std::string s;
1671 impl::SFormatResult ctx(impl::StringOutput(s.max_size(), s), fmt);
1672 constexpr const impl::FormatParser p;
1673 if constexpr( 0 < sizeof...(Targs) ) {
1674 ((p.template parseOne<Targs>(ctx, args)), ...);
1675 }
1676 p.template parseOne<impl::no_type_t>(ctx, impl::no_type_t());
1677 return s;
1678 }
1679
1680 /**
1681 * Strict format with type validation of arguments against the format string.
1682 *
1683 * See @ref jau_cfmt_header for details
1684 *
1685 * @tparam Targs the argument template type pack to be validated against the format string
1686 * @param maxLen maximum string length
1687 * @param fmt the snprintf format string
1688 * @param args passed arguments, used for template type deduction only
1689 * @return true if successfully parsed format and arguments, false otherwise.
1690 * @see @ref jau_cfmt_header
1691 */
1692 template <typename... Targs>
1693 inline std::string format(size_t maxLen, std::string_view fmt, const Targs &...args) noexcept {
1694 std::string s;
1695 impl::SFormatResult ctx(impl::StringOutput(std::min(maxLen, s.max_size()), s), fmt);
1696 constexpr const impl::FormatParser p;
1697 if constexpr( 0 < sizeof...(Targs) ) {
1698 ((p.template parseOne<Targs>(ctx, args)), ...);
1699 }
1700 p.template parseOne<impl::no_type_t>(ctx, impl::no_type_t());
1701 return s;
1702 }
1703
1704 /**
1705 * Strict format with type validation of arguments against the format string,
1706 * appending to the given destination.
1707 *
1708 * See @ref jau_cfmt_header for details
1709 *
1710 * @tparam Targs the argument template type pack to be validated against the format string
1711 * @param s destination string to append the formatted string
1712 * @param fmt the snprintf format string
1713 * @param args passed arguments, used for template type deduction only
1714 * @return jau::cfmt::Result instance for further inspection
1715 * @see @ref jau_cfmt_header
1716 */
1717 template <typename... Targs>
1718 inline Result formatR(std::string &s, std::string_view fmt, const Targs &...args) noexcept {
1719 impl::SFormatResult ctx(impl::StringOutput(s.max_size(), s), fmt);
1720 constexpr const impl::FormatParser p;
1721 if constexpr( 0 < sizeof...(Targs) ) {
1722 ((p.template parseOne<Targs>(ctx, args)), ...);
1723 }
1724 p.template parseOne<impl::no_type_t>(ctx, impl::no_type_t());
1725 return ctx;
1726 }
1727 /**
1728 * Strict format with type validation of arguments against the format string,
1729 * appending to the given destination.
1730 *
1731 * See @ref jau_cfmt_header for details
1732 *
1733 * @tparam Targs the argument template type pack to be validated against the format string
1734 * @param s destination string to append the formatted string
1735 * @param maxLen maximum string length
1736 * @param fmt the snprintf format string
1737 * @param args passed arguments, used for template type deduction only
1738 * @return jau::cfmt::Result instance for further inspection
1739 * @see @ref jau_cfmt_header
1740 */
1741 template <typename... Targs>
1742 inline Result formatR(std::string &s, size_t maxLen, std::string_view fmt, const Targs &...args) noexcept {
1743 impl::SFormatResult ctx(impl::StringOutput(std::min(maxLen, s.max_size()), s), fmt);
1744 constexpr const impl::FormatParser p;
1745 if constexpr( 0 < sizeof...(Targs) ) {
1746 ((p.template parseOne<Targs>(ctx, args)), ...);
1747 }
1748 p.template parseOne<impl::no_type_t>(ctx, impl::no_type_t());
1749 return ctx;
1750 }
1751
1752 /**
1753 * Strict type validation of arguments against the format string.
1754 *
1755 * See @ref jau_cfmt_header for details
1756 *
1757 * @tparam Targs the argument template type pack to be validated against the format string
1758 * @param fmt the snprintf format string
1759 * @param args passed arguments, used for template type deduction only
1760 * @return number of parser format arguments if successfully, otherwise negative number indicates first failed argument starting with -1
1761 * w/ min(ssize_t) denoting the format string
1762 * @see @ref jau_cfmt_header
1763 */
1764 template <typename... Targs>
1765 consteval_cxx20 ssize_t check(std::string_view fmt, const Targs &...) noexcept {
1767 constexpr const impl::CheckParser p;
1768 if constexpr( 0 < sizeof...(Targs) ) {
1769 ((p.template checkOne<Targs>(ctx)), ...);
1770 }
1771 p.template checkOne<impl::no_type_t>(ctx);
1772 return ctx.arg_count;
1773 }
1774 /**
1775 * Strict type validation of arguments against the format string.
1776 *
1777 * See @ref jau_cfmt_header for details
1778 *
1779 * @tparam Targs the argument template type pack to be validated against the format string
1780 * @param fmt the snprintf format string
1781 * @param args passed arguments, used for template type deduction only
1782 * @return 0 if successfully, otherwise the source code line number detecting the failure
1783 * @see @ref jau_cfmt_header
1784 */
1785 template <typename... Targs>
1786 consteval_cxx20 int checkLine(std::string_view fmt, const Targs &...) noexcept {
1788 constexpr const impl::CheckParser p;
1789 if constexpr( 0 < sizeof...(Targs) ) {
1790 ((p.template checkOne<Targs>(ctx)), ...);
1791 }
1792 p.template checkOne<impl::no_type_t>(ctx);
1793 return ctx.line;
1794 }
1795
1796 /**
1797 * Strict type validation of arguments against the format string.
1798 *
1799 * See @ref jau_cfmt_header for details
1800 *
1801 * @tparam Targs the argument template type pack to be validated against the format string
1802 * @param fmt the snprintf format string
1803 * @return number of parser format arguments if successfully, otherwise negative number indicates first failed argument starting with -1
1804 * w/ min(ssize_t) denoting the format string
1805 * @see @ref jau_cfmt_header
1806 */
1807 template <typename... Targs>
1808 consteval_cxx20 ssize_t check2(std::string_view fmt) noexcept {
1810 constexpr const impl::CheckParser p;
1811 if constexpr( 0 < sizeof...(Targs) ) {
1812 ((p.template checkOne<Targs>(ctx)), ...);
1813 }
1814 p.template checkOne<impl::no_type_t>(ctx);
1815 return ctx.arg_count;
1816 }
1817
1818 /**
1819 * Strict type validation of arguments against the format string.
1820 *
1821 * See @ref jau_cfmt_header for details
1822 *
1823 * @tparam Targs the argument template type pack to be validated against the format string
1824 * @param fmt the snprintf format string
1825 * @return 0 if successfully, otherwise the source code line number detecting the failure
1826 * @see @ref jau_cfmt_header
1827 */
1828 template <typename... Targs>
1829 consteval_cxx20 int check2Line(std::string_view fmt) noexcept {
1831 constexpr const impl::CheckParser p;
1832 if constexpr( 0 < sizeof...(Targs) ) {
1833 ((p.template checkOne<Targs>(ctx)), ...);
1834 }
1835 p.template checkOne<impl::no_type_t>(ctx);
1836 return ctx.line;
1837 }
1838
1839 /**
1840 * Strict type validation of arguments against the format string.
1841 *
1842 * See @ref jau_cfmt_header for details
1843 *
1844 * @tparam Targs the argument template type pack to be validated against the format string
1845 * @param fmt the snprintf format string
1846 * @param args passed arguments, used for template type deduction only
1847 * @return jau::cfmt::Result instance for further inspection
1848 * @see @ref jau_cfmt_header
1849 */
1850 template <typename... Targs>
1851 consteval_cxx20 Result checkR(std::string_view fmt, const Targs &...) noexcept {
1853 constexpr const impl::CheckParser p;
1854 if constexpr( 0 < sizeof...(Targs) ) {
1855 ((p.template checkOne<Targs>(ctx)), ...);
1856 }
1857 p.template checkOne<impl::no_type_t>(ctx);
1858 return ctx;
1859 }
1860
1861 /**
1862 * Strict type validation of arguments against the format string.
1863 *
1864 * See @ref jau_cfmt_header for details
1865 *
1866 * @tparam Targs the argument template type pack to be validated against the format string
1867 * @param fmt the snprintf format string
1868 * @return jau::cfmt::Result instance for further inspection
1869 * @see @ref jau_cfmt_header
1870 */
1871 template <typename... Targs>
1872 consteval_cxx20 Result checkR2(std::string_view format) noexcept {
1874 constexpr const impl::CheckParser p;
1875 if constexpr( 0 < sizeof...(Targs) ) {
1876 ((p.template checkOne<Targs>(ctx)), ...);
1877 }
1878 p.template checkOne<impl::no_type_t>(ctx);
1879 return ctx;
1880 }
1881
1882 /**@}*/
1883
1884} // namespace jau::cfmt
1885
1886namespace jau {
1887 /**
1888 * Safely returns a (potentially truncated) string according to `snprintf()` formatting rules
1889 * and variable number of arguments following the `format` argument.
1890 *
1891 * jau::cfmt::format() is utilize to validate `format` against given arguments at *runtime*.
1892 *
1893 * Resulting string is truncated to `min(maxStrLen, formatLen)`,
1894 * with `formatLen` being the given formatted string length of output w/o limitation.
1895 *
1896 * Use `std::string::shrink_to_fit()` on the returned string,
1897 * if you desire efficiency for longer lifecycles.
1898 *
1899 * See @ref jau_cfmt_header for details
1900 *
1901 * @param maxStrLen maximum resulting string length including
1902 * @param format `printf()` compliant format string
1903 * @param args optional arguments matching the format string
1904 */
1905 template<typename... Args>
1906 inline std::string format_string_n(const std::size_t maxStrLen, std::string_view format, const Args &...args) noexcept {
1907 return jau::cfmt::format(maxStrLen, format, args...);
1908 }
1909
1910 /**
1911 * Safely returns a (non-truncated) string according to `snprintf()` formatting rules
1912 * and variable number of arguments following the `format` argument.
1913 *
1914 * jau::cfmt::formatR() is utilize to validate `format` against given arguments at *runtime*.
1915 *
1916 * Resulting string size matches formated output w/o limitation
1917 * and its capacity is left unchanged.
1918 *
1919 * Use `std::string::shrink_to_fit()` on the returned string,
1920 * if you desire efficiency for longer lifecycles.
1921 *
1922 * See @ref jau_cfmt_header for details
1923 *
1924 * @param strLenHint initially used string length w/o EOS
1925 * @param format `printf()` compliant format string
1926 * @param args optional arguments matching the format string
1927 */
1928 template <typename... Args>
1929 inline std::string format_string_h(const std::size_t strLenHint, std::string_view format, const Args &...args) noexcept {
1930 std::string str;
1931 std::exception_ptr eptr;
1932 try {
1933 str.reserve(strLenHint);
1934 jau::cfmt::formatR(str, format, args...);
1935 } catch (...) {
1936 eptr = std::current_exception();
1937 }
1938 handle_exception(eptr);
1939 return str;
1940 }
1941
1942 /**
1943 * Safely returns a (non-truncated) string according to `snprintf()` formatting rules
1944 * using a reserved string length of jau::cfmt::default_string_capacity and
1945 * variable number of arguments following the `format` argument.
1946 *
1947 * jau::cfmt::formatR() is utilize to validate `format` against given arguments at *runtime*.
1948 *
1949 * Resulting string size matches formated output w/o limitation
1950 * and its capacity is left unchanged.
1951 *
1952 * Use `std::string::shrink_to_fit()` on the returned string,
1953 * if you desire efficiency for longer lifecycles.
1954 *
1955 * See @ref jau_cfmt_header for details
1956 *
1957 * @param format `printf()` compliant format string
1958 * @param args optional arguments matching the format string
1959 */
1960 template <typename... Args>
1961 inline std::string format_string(std::string_view format, const Args &...args) noexcept {
1963 }
1964
1965 /**@}*/
1966
1967} // namespace jau
1968
1969/** \addtogroup StringUtils
1970 *
1971 * @{
1972 */
1973
1974/**
1975 * Macro, safely returns a (non-truncated) string according to `snprintf()` formatting rules
1976 * using a reserved string length of jau::cfmt::default_string_capacity and
1977 * variable number of arguments following the `format` argument.
1978 *
1979 * This macro also produces compile time validation using a `static_assert`
1980 * against jau::cfmt::check2.
1981 *
1982 * jau::cfmt::formatR() is utilize to validate `format` against given arguments at *runtime*.
1983 *
1984 * Resulting string size matches formated output w/o limitation
1985 * and its capacity is left unchanged.
1986 *
1987 * Use `std::string::shrink_to_fit()` on the returned string,
1988 * if you desire efficiency for longer lifecycles.
1989 *
1990 * See @ref jau_cfmt_header for details
1991 *
1992 * @param format `printf()` compliant format string
1993 * @param args optional arguments matching the format string
1994 */
1995#define jau_format_string(fmt, ...) \
1996 jau::format_string((fmt) __VA_OPT__(,) __VA_ARGS__); \
1997 static_assert(0 <= jau::cfmt::check2< JAU_FOR_EACH1_LIST(JAU_DECLTYPE_VALUE, __VA_ARGS__) >(fmt)); // compile time validation!
1998
1999/**
2000 * Macro, safely returns a (non-truncated) string according to `snprintf()` formatting rules
2001 * and variable number of arguments following the `format` argument.
2002 *
2003 * This macro also produces compile time validation using a `static_assert`
2004 * against jau::cfmt::check2.
2005 *
2006 * jau::cfmt::formatR() is utilize to validate `format` against given arguments at *runtime*.
2007 *
2008 * Resulting string size matches formated output w/o limitation
2009 * and its capacity is left unchanged.
2010 *
2011 * Use `std::string::shrink_to_fit()` on the returned string,
2012 * if you desire efficiency for longer lifecycles.
2013 *
2014 * See @ref jau_cfmt_header for details
2015 *
2016 * @param strLenHint initially used string length w/o EOS
2017 * @param format `printf()` compliant format string
2018 * @param args optional arguments matching the format string
2019 */
2020#define jau_format_string_h(strLenHint, fmt, ...) \
2021 jau::format_string((strLenHint), (fmt) __VA_OPT__(,) __VA_ARGS__); \
2022 static_assert(0 <= jau::cfmt::check2< JAU_FOR_EACH1_LIST(JAU_DECLTYPE_VALUE, __VA_ARGS__) >(fmt)); // compile time validation!
2023
2024/**
2025 * Macro, safely returns a (non-truncated) string according to `snprintf()` formatting rules
2026 * using a reserved string length of jau::cfmt::default_string_capacity and
2027 * variable number of arguments following the `format` argument.
2028 *
2029 * This macro also produces compile time validation using a `static_assert`
2030 * against jau::cfmt::check2Line.
2031 *
2032 * jau::cfmt::formatR() is utilize to validate `format` against given arguments at *runtime*.
2033 *
2034 * Resulting string size matches formated output w/o limitation
2035 * and its capacity is left unchanged.
2036 *
2037 * Use `std::string::shrink_to_fit()` on the returned string,
2038 * if you desire efficiency for longer lifecycles.
2039 *
2040 * See @ref jau_cfmt_header for details
2041 *
2042 * @param format `printf()` compliant format string
2043 * @param args optional arguments matching the format string
2044 */
2045#define jau_format_string2(fmt, ...) \
2046 jau::format_string((fmt) __VA_OPT__(,) __VA_ARGS__); \
2047 static_assert(0 == jau::cfmt::check2Line< JAU_FOR_EACH1_LIST(JAU_DECLTYPE_VALUE, __VA_ARGS__) >(fmt)); // compile time validation!
2048
2049/**
2050 * Macro produces compile time validation using a `static_assert`
2051 * against jau::cfmt::check2.
2052 *
2053 * See @ref jau_cfmt_header for details
2054 *
2055 * @param format `printf()` compliant format string
2056 * @param args optional arguments matching the format string
2057 */
2058#define jau_string_check(fmt, ...) \
2059 static_assert(0 <= jau::cfmt::check2< JAU_FOR_EACH1_LIST(JAU_DECLTYPE_VALUE, __VA_ARGS__) >(fmt)); // compile time validation!
2060
2061/**
2062 * Macro produces compile time validation using a `static_assert`
2063 * against jau::cfmt::check2Line.
2064 *
2065 * See @ref jau_cfmt_header for details
2066 *
2067 * @param format `printf()` compliant format string
2068 * @param args optional arguments matching the format string
2069 */
2070#define jau_string_checkLine(fmt, ...) \
2071 static_assert(0 == jau::cfmt::check2Line< JAU_FOR_EACH1_LIST(JAU_DECLTYPE_VALUE, __VA_ARGS__) >(fmt)); // compile time validation!
2072
2073/**@}*/
2074
2075/** \example test_stringfmt_format.cpp
2076 * This C++ unit test validates most of the supported format specifiers against its arguments.
2077 */
2078
2079/** \example test_stringfmt_perf.cpp
2080 * This C++ unit test benchmarks the jau::cfmt implementation against `snprintf` and `std::ostringstream`.
2081 */
2082
2083/** \example test_stringfmt_check.cpp
2084 * This C++ unit test validates specific aspects of the implementation.
2085 */
2086
2087#endif // JAU_STRING_CFMT_HPP_
constexpr ssize_t argumentCount() const noexcept
Arguments processed.
std::string toString() const
constexpr const std::string_view & fmt() const noexcept
format string_view
constexpr int errorLine() const noexcept
error line of implementation source code or zero if success (error analysis)
constexpr const FormatOpts & opts() const noexcept
Last argument FormatOpts (error analysis)
constexpr Result(std::string_view f, FormatOpts o, size_t pos, ssize_t acount, int line, bool ok)
constexpr bool success() const noexcept
true if operation was successful, otherwise indicates error
constexpr size_t pos() const noexcept
Position of next fmt character to be read (error analysis)
constexpr FResult(Output p, std::string_view fmt_) noexcept
constexpr FResult & operator=(const FResult &x) noexcept=default
constexpr ssize_t argCount() const noexcept
std::string toString() const
constexpr FResult(const FResult &pre) noexcept=default
constexpr bool hasNext() const noexcept
constexpr bool error() const noexcept
A null OutputType for constexpr and consteval formatting dropping all output.
constexpr void appendText(std::string_view) noexcept
constexpr size_t maxLen() const noexcept
constexpr bool fits(size_t) const noexcept
std::string_view get() const noexcept
constexpr void appendError(size_t, int, const std::string_view) noexcept
constexpr void appendFormatted(const FormatOpts &, const T &) noexcept
constexpr NullOutput() noexcept=default
constexpr void appendFormattedInt(const FormatOpts &, const T &, bool) noexcept
constexpr void appendFormattedFloat(const FormatOpts &, const T &, nsize_t) noexcept
constexpr void parseOne(Result &pc, const T &val) const noexcept
constexpr void parseOne(Result &pc, const T &val) const noexcept
constexpr void checkOne(Result &pc) const noexcept
constexpr void parseOne(Result &pc, const T &val) const noexcept
constexpr void parseOne(Result &pc, const T &val) const noexcept
constexpr void checkOne(Result &pc) const noexcept
constexpr void checkOne(Result &pc) const noexcept
FResult< Output > Result
constexpr void parseOne(Result &pc, const T &val) const noexcept
constexpr Parser() noexcept=default
constexpr void checkOne(Result &pc) const noexcept
constexpr void checkOne(Result &pc) const noexcept
A std::string OutputType for runtime formatting into a std::string.
void appendFormatted(const FormatOpts &opts, const T &v) noexcept
void appendText(const std::string_view v) noexcept
constexpr bool fits(size_t n) const noexcept
std::string_view get() const noexcept
void appendFormattedFloat(const FormatOpts &opts, const T &v, nsize_t floatSize) noexcept
void appendError(size_t argIdx, int line, const std::string_view tag) noexcept
StringOutput(std::size_t maxLen, std::string &s) noexcept
void appendFormatted(const FormatOpts &opts, const T &v) noexcept
constexpr size_t maxLen() const noexcept
void appendFormatted(const FormatOpts &opts, const T &v) noexcept
void appendFormattedInt(const FormatOpts &opts, const T &v, bool negative) noexcept
void appendFormatted(const FormatOpts &opts, const T &v) noexcept
Concept of type-trait for bool type.
Concept of type-trait std::is_pointer.
Concept of type-trait std::is_signed and std::is_integral.
A string class, i.e. std::string, std::string_view or jau::StringLiteral.
A string literal: char (&)[N], jau::StringLiteral.
A convertible type to a string or a string itself.
Concept of type-trait std::is_unsigned and std::is_integral.
std::string to_string(const endian_t v) noexcept
Return std::string representation of the given endian.
constexpr uint16_t cpu_to_le(uint16_t const h) noexcept
#define JAU_MAKE_ENUM_STRING(type,...)
constexpr bool value(const Bool rhs) noexcept
#define JAU_MAKE_BITFIELD_ENUM_STRING(type,...)
constexpr bool is_set(const E mask, const E bits) noexcept
std::ostream & operator<<(std::ostream &os, const T v)
std::add_const_t< std::add_pointer_t< std::add_const_t< underlying_pointer_type< T > > > > const2_pointer
Returns all const pointer type w/ constant'ness in pointer and value.
#define consteval_cxx20
consteval qualifier replacement for C++20 consteval.
bool handle_exception(std::exception_ptr eptr)
Handle given optional exception (nullable std::exception_ptr) and send std::exception::what() message...
constexpr bool is_zero(const T &a, const T &epsilon=std::numeric_limits< T >::epsilon()) noexcept
Returns true if the given value is less than epsilon, w/ epsilon > 0.
constexpr T invert_sign(const T x) noexcept
Safely inverts the sign of an arithmetic number w/ branching in O(1)
constexpr int sign(const T x) noexcept
Returns the value of the sign function (w/o branching ?) in O(1).
uint_bytes_t< sizeof(unsigned long int)> nsize_t
Natural 'size_t' alternative using uint<XX>_t with xx = sizeof(unsigned long int)*8 as its natural si...
Definition int_types.hpp:85
constexpr T abs(const T x) noexcept
Returns the absolute value of an arithmetic number (w/ branching) in O(1)
plength_t
Format length modifiers.
constexpr bool from_chars(value_type &result, std::string_view str) noexcept
flags_t
Format flags.
constexpr bool is_digit(char c) noexcept
Result formatR(std::string &s, std::string_view fmt, const Targs &...args) noexcept
Strict format with type validation of arguments against the format string, appending to the given des...
cspec_t
Format conversion specifier (fully defined w/ radix)
constexpr const size_t num_max_slen
Maximum net number string len w/o EOS, up to uint64_t.
static constexpr const char * to_string(pstate_t s) noexcept
consteval_cxx20 int checkLine(std::string_view fmt, const Targs &...) noexcept
Strict type validation of arguments against the format string.
consteval_cxx20 ssize_t check2(std::string_view fmt) noexcept
Strict type validation of arguments against the format string.
consteval_cxx20 Result checkR(std::string_view fmt, const Targs &...) noexcept
Strict type validation of arguments against the format string.
consteval_cxx20 Result checkR2(std::string_view format) noexcept
Strict type validation of arguments against the format string.
consteval_cxx20 int check2Line(std::string_view fmt) noexcept
Strict type validation of arguments against the format string.
constexpr const size_t default_string_capacity
Default string reserved capacity w/o EOS (511)
consteval_cxx20 ssize_t check(std::string_view fmt, const Targs &...) noexcept
Strict type validation of arguments against the format string.
std::string format(std::string_view fmt, const Targs &...args) noexcept
Strict format with type validation of arguments against the format string.
@ j
intmax_t or uintmax_t integer
@ ll
long long integer
@ L
long double float
@ z
size_t or ssize_t integer
@ hash
actual flag #, C99
@ uppercase
uppercase, via conversion spec
@ left
actual flag -, C99
@ thousands
actual flag ‘&rsquo;`, POSIX
@ plus
actual flag +, C99
@ zeropad
actual flag 0, C99
@ space
actual flag , C99
@ unsigned_int
o, x or X, u, b
typename std::conditional_t< std::is_integral_v< T >||jau::req::boolean< T >, std::type_identity< uint64_t >, std::type_identity< T > >::type make_int_unsigned_t
Returns uint64_t type if: integral || boolean, otherwise returns orig type.
FResult< StringOutput > SFormatResult
A StringOutput formatting result for runtime formatting into a std::string.
constexpr T abs_int(const T x) noexcept
void append_integral(std::string &dest, const size_t dest_maxlen, const uint64_t val, bool negative, const FormatOpts &opts, bool inject_dot=false) noexcept
typename std::conditional_t< jau::req::pointer< T >, std::type_identity< const char *const >, std::type_identity< T > >::type make_simple_pointer_t
Returns a simple const char * const if: pointer, otherwise returns orig type.
FResult< NullOutput > CheckResult
Parser< StringOutput > FormatParser
void append_afloatF64(std::string &dest, const size_t dest_maxlen, const double ivalue, const size_t ivalue_size, const FormatOpts &iopts) noexcept
typename std::conditional_t< jau::req::unsigned_integral< T > &&!jau::req::boolean< T >, std::make_signed< T >, std::type_identity< T > >::type make_int_signed_t
Returns signed-type variation if: unsigned-integral && !boolean, otherwise returns orig type.
constexpr bool is_positive(const T a) noexcept
void append_rev(std::string &dest, const size_t dest_maxlen, std::string_view src, bool prec_cut, bool reverse, const FormatOpts &opts) noexcept
bool is_float_validT(std::string &dest, const size_t dest_maxlen, const value_type value, const FormatOpts &opts) noexcept
void append_string(std::string &dest, const size_t dest_maxlen, std::string_view src, const FormatOpts &opts) noexcept
void append_floatF64(std::string &dest, const size_t dest_maxlen, const double value, const FormatOpts &opts) noexcept
constexpr const double_t max_append_float
constexpr const size_t float_charbuf_maxlen
void append_efloatF64(std::string &dest, const size_t dest_maxlen, const double ivalue, const FormatOpts &iopts) noexcept
bool is_float_validF64(std::string &dest, const size_t dest_maxlen, const double value, const FormatOpts &opts) noexcept
constexpr const size_t default_float_precision
Parser< NullOutput > CheckParser
Author: Sven Gothel sgothel@jausoft.com Copyright (c) 2021-2026 Gothel Software e....
Author: Sven Gothel sgothel@jausoft.com Copyright Gothel Software e.K.
Definition enum_util.hpp:65
__pack(...): Produces MSVC, clang and gcc compatible lead-in and -out macros.
Definition backtrace.hpp:32
std::string format_string_h(const std::size_t strLenHint, std::string_view format, const Args &...args) noexcept
Safely returns a (non-truncated) string according to snprintf() formatting rules and variable number ...
std::string format_string_n(const std::size_t maxStrLen, std::string_view format, const Args &...args) noexcept
Safely returns a (potentially truncated) string according to snprintf() formatting rules and variable...
std::string format_string(std::string_view format, const Args &...args) noexcept
Safely returns a (non-truncated) string according to snprintf() formatting rules using a reserved str...
STL namespace.
constexpr void setPrecision(size_t v)
constexpr bool setConversion(char fmt_literal) noexcept
constexpr bool addFlag(char c) noexcept
std::string toString() const
constexpr void reset() noexcept
constexpr void validateFlags() noexcept
constexpr void setWidth(size_t v)
constexpr FormatOpts() noexcept
std::string toFormat() const
Reconstructs format string.
std::string_view fmt
static std::string f(uint32_t v)