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