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