jaulib v1.3.6
Jau Support Library (C++, Java, ..)
Loading...
Searching...
No Matches
string_cfmt2.hpp
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2024 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_CFMT2_HPP_
16#define JAU_STRING_CFMT2_HPP_
17
18#include <sys/types.h>
19#include <cassert>
20#include <cerrno>
21#include <cstddef>
22#include <cstdio>
23#include <cstdlib>
24#include <cstring>
25#include <limits>
26#include <string>
27#include <string_view>
28#include <type_traits>
29#include <iostream>
30
31#include <jau/byte_util.hpp>
32#include <jau/cpp_lang_util.hpp>
35
36#include <jau/cpp_pragma.hpp>
37
38/**
39 * @anchor jau_cfmt_header
40 * ## `snprintf` argument type checker `jau::cfmt`
41 *
42 * ### Features
43 * - jau::cfmt::check() provides strict type matching of arguments against the format string.
44 * - Have jau::cfmt::check() to be passed _before_ using std::snprintf(),
45 * removing safety concerns of the latter and benefit from its formatting and performance.
46 * - Follows [C++ Reference](https://en.cppreference.com/w/cpp/io/c/fprintf)
47 *
48 * ### Type Conversion
49 * Implementation follows type conversion rules as described
50 * in [Variadic Default Conversion](https://en.cppreference.com/w/cpp/language/variadic_arguments#Default_conversions)
51 * - float to double promotion
52 * - bool, char, short, and unscoped enumerations are converted to int or wider integer types
53 * as well as in [va_arg](https://en.cppreference.com/w/cpp/utility/variadic/va_arg)
54 * - ignore signed/unsigned type differences for integral types
55 * - void pointer tolerance
56 *
57 * ### Implementation Details
58 * - Validates arguments against format string at compile time or runtime,
59 * depending whether passed arguments are of constexpr nature (compile time).
60 * - Written in C++20 using template argument pack w/ save argument type checks
61 * - Written as constexpr, capable to be utilized at compile-time.
62 *
63 * #### Supported Conversion Specifiers and Data Types
64 *
65 * The following conversion specifiers are supported:
66 * - `c`, `s`, `d`, `o`, `x`, `X`, `u`, `f`, `e`, `E`, `a`, `A`, `g`, `G`, `p`
67 * - Their synonyms
68 * - `i` -> `d`
69 * - `F` -> `f`
70 * - Flags `-`, `+`, ` `, `0` and `#`
71 * - Asterisk `*` for field width and precision
72 *
73 * The following length modifiers are supported where allowed
74 * - `hh` [unsigned] char
75 * - `h` [unsigned] short
76 * - `l` [unsigned] long
77 * - `ll` [unsigned] long long
78 * - 'j' uintmax_t or intmax_t
79 * - 'z' size_t or ssize_t
80 * - 't' ptrdiff_t
81 * - 'L' long double
82 *
83 * See [C++ Reference](https://en.cppreference.com/w/cpp/io/c/fprintf) for details.
84 *
85 * ### Further Documentation
86 * - [C++ Reference](https://en.cppreference.com/w/cpp/io/c/fprintf)
87 * - [Linux snprintf(3) man page](https://www.man7.org/linux/man-pages/man3/snprintf.3p.html)
88 * - [FreeBSD snprintf(3) man page](https://man.freebsd.org/cgi/man.cgi?snprintf(3))
89 */
90namespace jau::cfmt2 {
91
92 /** \addtogroup StringUtils
93 *
94 * @{
95 */
96
104 constexpr static const char *to_string(pstate_t s) noexcept {
105 switch( s ) {
106 case pstate_t::outside: return "outside";
107 case pstate_t::start: return "start";
108 case pstate_t::field_width: return "width";
109 case pstate_t::precision: return "precision";
110 default: return "error";
111 }
112 }
124 constexpr static const char *to_string(plength_t s) noexcept {
125 switch( s ) {
126 case plength_t::hh: return "hh";
127 case plength_t::h: return "h";
128 case plength_t::none: return "";
129 case plength_t::l: return "l";
130 case plength_t::ll: return "ll";
131 case plength_t::j: return "j";
132 case plength_t::z: return "z";
133 case plength_t::t: return "t";
134 case plength_t::L: return "L";
135 default: return "n/a";
136 }
137 }
138
139 static constexpr bool isDigit(const char c) noexcept { return '0' <= c && c <= '9'; }
140
141 namespace impl {
142 template <typename T>
143 class Parser; // fwd
144 }
145
146 struct PResult {
147 const std::string_view fmt;
148 const size_t pos;
149 const ssize_t arg_count;
150 const int line;
153 const bool precision_set;
154
155 template <size_t N>
156 constexpr PResult(const char (&fmt_)[N]) noexcept
158
159 constexpr PResult(const std::string_view fmt_) noexcept
161
162 constexpr PResult(const PResult &pre) noexcept = default;
163
164 constexpr PResult &operator=(const PResult &x) noexcept = delete;
165
166 constexpr bool hasNext() const noexcept {
167 return !error() && pos < fmt.length() - 1;
168 }
169
170 constexpr ssize_t argCount() const noexcept { return arg_count; }
171 constexpr bool error() const noexcept { return pstate_t::error == state; }
172 constexpr char sym() const noexcept { return fmt[pos]; }
173
174 std::string toString() const {
175 const char c = pos < fmt.length() ? fmt[pos] : '@';
176 std::string s = "args ";
177 s.append(std::to_string(arg_count))
178 .append(", state ")
179 .append(to_string(state))
180 .append(", line ")
181 .append(std::to_string(line))
182 .append(", pos ")
183 .append(std::to_string(pos))
184 .append(", char `")
185 .append(std::string(1, c))
186 .append("`, length `")
187 .append(to_string(length_mod))
188 .append("`, precision ")
189 .append(std::to_string(precision_set))
190 .append(", fmt `")
191 .append(fmt)
192 .append("`");
193 return s;
194 }
195
196 private:
197 template <typename T>
198 friend class impl::Parser;
199
200 constexpr PResult(const PResult &pre, size_t pos2) noexcept
201 : fmt(pre.fmt), pos(pos2), arg_count(pre.arg_count), line(pre.line), state(pre.state), length_mod(pre.length_mod), precision_set(pre.precision_set) {}
202
203 constexpr PResult(const PResult &pre, size_t pos2, ssize_t arg_count2) noexcept
204 : fmt(pre.fmt), pos(pos2), arg_count(arg_count2), line(pre.line), state(pre.state), length_mod(pre.length_mod), precision_set(pre.precision_set) {}
205
206 constexpr PResult(const PResult &pre, pstate_t state2) noexcept
207 : fmt(pre.fmt), pos(pre.pos), arg_count(pre.arg_count), line(pre.line), state(state2), length_mod(pre.length_mod), precision_set(pre.precision_set) {}
208
209 constexpr PResult(const PResult &pre, pstate_t state2, size_t pos2) noexcept
210 : fmt(pre.fmt), pos(pos2), arg_count(pre.arg_count), line(pre.line), state(state2), length_mod(pre.length_mod), precision_set(pre.precision_set) {}
211
212 constexpr PResult(const PResult &pre, pstate_t state2, size_t pos2, ssize_t arg_count2, int line2) noexcept
213 : fmt(pre.fmt), pos(pos2), arg_count(arg_count2), line(line2), state(state2), length_mod(pre.length_mod), precision_set(pre.precision_set) {}
214
215 constexpr PResult(const PResult &pre, plength_t length_mod2, bool precision_set2) noexcept
216 : fmt(pre.fmt), pos(pre.pos), arg_count(pre.arg_count), line(pre.line), state(pre.state), length_mod(length_mod2), precision_set(precision_set2) {}
217
218 constexpr PResult nextSymbol() const noexcept {
219 if( pos < fmt.length() - 1 ) {
220 return PResult(*this, pos+1);
221 } else {
222 return *this;
223 }
224 }
225 constexpr PResult toConversion() const noexcept {
226 if( pstate_t::outside != state ) {
227 return PResult(*this); // inside conversion specifier
228 } else if( fmt[pos] == '%' ) {
229 return PResult(*this, pstate_t::start, pos); // just at start of conversion specifier
230 } else {
231 // seek next conversion specifier
232 const size_t q = fmt.find('%', pos + 1);
233 if( q == std::string::npos ) {
234 // no conversion specifier found
235 return PResult(*this, fmt.length());
236 } else {
237 // new conversion specifier found
238 return PResult(*this, pstate_t::start, q);
239 }
240 }
241 }
242
243 constexpr PResult setError(int l) const noexcept {
244 ssize_t arg_count2;
245 if( 0 == arg_count ) {
246 arg_count2 = std::numeric_limits<ssize_t>::min();
247 } else if( 0 < arg_count ) {
248 arg_count2 = arg_count * -1;
249 } else {
250 arg_count2 = arg_count;
251 }
252 return PResult(*this, pstate_t::error, pos, arg_count2, l);
253 }
254 };
255
256 inline std::ostream &operator<<(std::ostream &out, const PResult &pc) {
257 out << pc.toString();
258 return out;
259 }
260
261 namespace impl {
262
263 static constexpr bool verbose_error = true;
264
265 enum class no_type_t {};
266
267 template <typename T>
268 class Parser {
269 public:
270 constexpr Parser() noexcept = delete;
271
272 /**
273 * Parse the given argument against the current conversion specifier of the format string.
274 *
275 * Multiple rounds of parsing calls might be required, each passing the next argument or null.
276 *
277 * Parsing is completed when method returns false, either signaling an error() or completion.
278 *
279 * @tparam T The type of the given argument
280 * @return true if no error _and_ not complete, i.e. further calls with subsequent parameter required. Otherwise parsing is done due to error or completeness.
281 */
282 static constexpr const PResult parseOne(const PResult pc) noexcept {
283 if( !pc.hasNext() ) {
284 return pc; // done or error
285 }
286
287 const PResult pc2 = pc.toConversion();
288 if( !pc2.hasNext() ) {
289 return pc2; // done
290 }
291
292 // pstate_t::outside != _state
293
294 /* skip '%' or previous `*` */
295 const PResult pc3 = pc2.nextSymbol();
296
297 if( pstate_t::start == pc3.state ) {
298 const PResult pc4 = parseFlags(PResult(pc3, pstate_t::field_width));
299
300 /* parse field width */
301 bool next_arg = false;
302 const PResult pc5 = parseFieldWidth(pc4, next_arg);
303 if( next_arg || pc5.error() ) {
304 return pc5; // error or continue with next argument for same conversion -> field_width
305 }
306 return parseP2(pc5);
307 } else {
308 return parseP2(pc3);
309 }
310 }
311
312 private:
313
314 static constexpr const PResult parseP2(const PResult pc) noexcept {
315 if( pstate_t::field_width == pc.state ) {
316 /* parse precision */
317 const PResult pc2 = PResult(pc, pstate_t::precision);
318 if( pc2.sym() == '.' ) {
319 const PResult pc3 = PResult(pc2, pc2.length_mod, true);
320 bool next_arg = false;
321 const PResult pc4 = parsePrecision(pc3, next_arg);
322 if( next_arg || pc4.error() ) {
323 return pc4; // error or continue with next argument for same conversion -> precision
324 }
325 return parseP3(pc4);
326 } else {
327 return parseP3(pc2);
328 }
329 } else {
330 return parseP3(pc);
331 }
332 }
333
334 static constexpr const PResult parseP3(const PResult pc) noexcept {
335 const PResult pc2 = parseLengthMods(pc);
336 if( pc2.error() ) {
337 return pc2; // error
338 }
339
340 const PResult pc3 = parseFmtSpec(pc2);
341 if( pc3.error() ) {
342 return pc3; // error
343 }
344
345 // next conversion specifier
346 const PResult pc4 = PResult(pc3, pstate_t::outside);
347 if( !pc4.hasNext() ) {
348 return pc4; // done
349 }
350 return PResult(pc4.nextSymbol(), plength_t::none, false); // clear length_mod and precision_set
351 }
352
353 static constexpr const PResult parseFlags(const PResult pc) noexcept {
354 switch( pc.sym() ) {
355 case '0': break;
356 case '-': break;
357 case '+': break;
358 case ' ': break;
359 case '#': break;
360 case '\'': break;
361 default: return pc; // done, not a flag
362 }
363 if( pc.hasNext() ) {
364 return parseFlags(pc.nextSymbol());
365 } else {
366 return pc;
367 }
368 }
369
370 static constexpr const PResult parseDigit(const PResult pc) noexcept {
371 if( !pc.hasNext() || !isDigit(pc.sym()) ) { return pc; }
372 return parseDigit( pc.nextSymbol() );
373 }
374
375 /* parse field width, returns true if parsing can continue or false if next argument is required or error */
376 static constexpr const PResult parseFieldWidth(const PResult pc, bool& next_arg) noexcept {
377 next_arg = false;
378 if( pc.sym() == '*' ) {
379 if( !pc.hasNext() ) { return pc; }
380 const PResult pc2 = pc.nextSymbol();
381
382 using U = std::remove_cv_t<T>;
383
384 if constexpr( std::is_same_v<no_type_t, T> ) {
385 return pc2.setError(__LINE__);
386 }
387 const PResult pc3 = PResult(pc2, pc.arg_count+1);
388 if constexpr( !std::is_same_v<int, U> ) {
389 return pc3.setError(__LINE__);
390 }
391 next_arg = true;
392 return pc3; // next argument is required
393 } else {
394 // continue with current argument
395 return parseDigit(pc);
396 }
397 }
398
399 /* parse precision, returns true if parsing can continue or false if next argument is required or error */
400 static constexpr const PResult parsePrecision(const PResult pc, bool &next_arg) noexcept {
401 next_arg = false;
402 if( !pc.hasNext() ) { return pc; }
403 const PResult pc2 = pc.nextSymbol();
404 const char c = pc.fmt[pc.pos];
405 if( c == '*' ) {
406 if( !pc2.hasNext() ) { return pc2; }
407 const PResult pc3 = pc2.nextSymbol();
408
409 using U = std::remove_cv_t<T>;
410
411 if constexpr( std::is_same_v<no_type_t, T> ) {
412 return pc3.setError(__LINE__);
413 }
414 const PResult pc4 = PResult(pc3, pc.arg_count+1);
415 if constexpr( !std::is_same_v<int, U> ) {
416 return pc4.setError(__LINE__);
417 }
418 next_arg = true;
419 return pc4; // next argument is required
420 } else {
421 // continue with current argument
422 return parseDigit(pc2);
423 }
424 }
425
426 static constexpr const PResult parseLengthMods(const PResult pc) noexcept {
427 const char sym = pc.sym();
428 if( 'h' == sym ) {
429 if( !pc.hasNext() ) { return pc.setError(__LINE__); }
430 const PResult pc2 = pc.nextSymbol();
431 if( 'h' == pc2.sym() ) {
432 if( !pc2.hasNext() ) { return pc2.setError(__LINE__); }
433 return PResult(pc2.nextSymbol(), plength_t::hh, pc2.precision_set);
434 } else {
435 return PResult(pc2, plength_t::h, pc2.precision_set);
436 }
437 } else if( 'l' == sym ) {
438 if( !pc.hasNext() ) { return pc.setError(__LINE__); }
439 const PResult pc2 = pc.nextSymbol();
440 if( 'l' == pc2.sym() ) {
441 if( !pc2.hasNext() ) { return pc2.setError(__LINE__); }
442 return PResult(pc2.nextSymbol(), plength_t::ll, pc2.precision_set);
443 } else {
444 return PResult(pc2, plength_t::l, pc2.precision_set);
445 }
446 } else if( 'j' == sym ) {
447 if( !pc.hasNext() ) { return pc.setError(__LINE__); }
448 return PResult(pc.nextSymbol(), plength_t::j, pc.precision_set);
449 } else if( 'z' == sym ) {
450 if( !pc.hasNext() ) { return pc.setError(__LINE__); }
451 return PResult(pc.nextSymbol(), plength_t::z, pc.precision_set);
452 } else if( 't' == sym ) {
453 if( !pc.hasNext() ) { return pc.setError(__LINE__); }
454 return PResult(pc.nextSymbol(), plength_t::t, pc.precision_set);
455 } else if( 'L' == sym ) {
456 if( !pc.hasNext() ) { return pc.setError(__LINE__); }
457 return PResult(pc.nextSymbol(), plength_t::L, pc.precision_set);
458 } else {
459 return PResult(pc, plength_t::none, pc.precision_set);
460 }
461 }
462
463 static constexpr const PResult parseFmtSpec(const PResult pc) noexcept {
464 const char fmt_literal = unaliasFmtSpec( pc.sym() );
465
466 switch( fmt_literal ) {
467 case '%':
468 case 'c':
469 case 's':
470 return parseStringFmtSpec(pc, fmt_literal);
471 case 'p':
472 return parseAPointerFmtSpec(pc);
473 case 'd':
474 return parseSignedFmtSpec(pc);
475 case 'o':
476 case 'x':
477 case 'X':
478 case 'u':
479 return parseUnsignedFmtSpec(pc, fmt_literal);
480 case 'f':
481 case 'e':
482 case 'E':
483 case 'a':
484 case 'A':
485 case 'g':
486 case 'G':
487 return parseFloatFmtSpec(pc, fmt_literal);
488 default:
489 return pc.setError(__LINE__);
490 } // switch( fmt_literal )
491 }
492
493 static constexpr char unaliasFmtSpec(const char fmt_literal) noexcept {
494 switch( fmt_literal ) {
495 case 'i': return 'd';
496 case 'F': return 'f';
497 default: return fmt_literal;
498 }
499 }
500
501 static constexpr const PResult parseStringFmtSpec(const PResult pc, const char fmt_literal) noexcept {
502 if constexpr( std::is_same_v<no_type_t, T> ) {
503 return pc.setError(__LINE__);
504 }
505 switch( fmt_literal ) {
506 case '%':
507 break;
508 case 'c': {
509 const PResult pc2 = PResult(pc, pc.pos, pc.arg_count+1);
510 using U = std::remove_cv_t<T>;
511 switch( pc2.length_mod ) {
512 case plength_t::none:
513 if constexpr( !std::is_same_v<char, U> ||
514 !std::is_same_v<int, U> ) {
515 return pc2.setError(__LINE__);
516 }
517 break;
518 case plength_t::l:
519 if constexpr( !std::is_same_v<wchar_t, U> ||
520 !std::is_same_v<wint_t, U> ) {
521 return pc2.setError(__LINE__);
522 }
523 break;
524 default:
525 return pc2.setError(__LINE__);
526 }
527 return pc2;
528 }
529 case 's': {
530 const PResult pc2 = PResult(pc, pc.pos, pc.arg_count+1);
531 switch( pc2.length_mod ) {
532 case plength_t::none:
533 if constexpr( !std::is_pointer_v<T> ||
534 !std::is_same_v<char, std::remove_cv_t<std::remove_pointer_t<T>>> ) {
535 return pc2.setError(__LINE__);
536 }
537 break;
538 case plength_t::l:
539 if constexpr( !std::is_pointer_v<T> ||
540 !std::is_same_v<wchar_t, std::remove_cv_t<std::remove_pointer_t<T>>> ) {
541 return pc2.setError(__LINE__);
542 }
543 break;
544 default:
545 return pc2.setError(__LINE__);
546 }
547 return pc2;
548 }
549 default: return pc.setError(__LINE__);
550 }
551 return pc;
552 }
553
554 static constexpr const PResult parseAPointerFmtSpec(const PResult pc) noexcept {
555 const PResult pc2 = PResult(pc, plength_t::none, pc.precision_set);
556 if constexpr( std::is_same_v<no_type_t, T> ) {
557 return pc2.setError(__LINE__);
558 }
559 const PResult pc3 = PResult(pc2, pc2.pos, pc2.arg_count+1);
560 if constexpr( !std::is_pointer_v<T> ) {
561 return pc3.setError(__LINE__);
562 }
563 return pc3;
564 }
565
566 static constexpr const PResult parseSignedFmtSpec(const PResult pc) noexcept {
567 if constexpr( std::is_same_v<no_type_t, T> ) {
568 return pc.setError(__LINE__);
569 }
570 const PResult pc2 = PResult(pc, pc.pos, pc.arg_count+1);
571
572 using U = std::remove_cv_t<T>;
573 // using U = std::conditional<std::is_integral_v<T> && std::is_unsigned_v<T>, std::make_signed<T>, T>::type; // triggers the instantiating the 'other' case and hence fails
574 using V = typename std::conditional_t<std::is_integral_v<U> && std::is_unsigned_v<U>, std::make_signed<U>, std::type_identity<U>>::type; // NOLINT
575
576 switch( pc2.length_mod ) {
577 case plength_t::hh:
578 if constexpr( !std::is_same_v<char, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(char)) ) {
579 return pc2.setError(__LINE__);
580 }
581 break;
582 case plength_t::h:
583 if constexpr( !std::is_same_v<short, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(short)) ) {
584 return pc2.setError(__LINE__);
585 }
586 break;
587 case plength_t::none:
588 if constexpr( !std::is_same_v<int, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(int)) ) {
589 return pc2.setError(__LINE__);
590 }
591 break;
592 case plength_t::l:
593 if constexpr( !std::is_same_v<long, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(long)) ) {
594 return pc2.setError(__LINE__);
595 }
596 break;
597 case plength_t::ll:
598 if constexpr( !std::is_same_v<long long, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(long long)) ) {
599 return pc2.setError(__LINE__);
600 }
601 break;
602 case plength_t::j:
603 if constexpr( !std::is_same_v<intmax_t, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(intmax_t)) ) {
604 return pc2.setError(__LINE__);
605 }
606 break;
607 case plength_t::z:
608 if constexpr( !std::is_same_v<ssize_t, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(ssize_t)) ) {
609 return pc2.setError(__LINE__);
610 }
611 break;
612 case plength_t::t:
613 if constexpr( !std::is_same_v<ptrdiff_t, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(ptrdiff_t)) ) {
614 return pc2.setError(__LINE__);
615 }
616 break;
617 default:
618 return pc2.setError(__LINE__);
619 }
620 return pc2;
621 }
622
623 static constexpr const PResult parseUnsignedFmtSpec(const PResult pc, const char /*fmt_literal*/) noexcept {
624 if constexpr( std::is_same_v<no_type_t, T> ) {
625 return pc.setError(__LINE__);
626 }
627 const PResult pc2 = PResult(pc, pc.pos, pc.arg_count+1);
628
629 using U = std::remove_cv_t<T>;
630 // using U = std::conditional_t<std::is_integral_v<T> && std::is_signed_v<T>, std::make_unsigned_t<T>, T>; // triggers the instantiating the 'other' case and hence fails
631 using V = typename std::conditional_t<std::is_integral_v<U> && std::is_signed_v<U>, std::make_unsigned<U>, std::type_identity<U>>::type; // NOLINT
632
633 switch( pc2.length_mod ) {
634 case plength_t::hh:
635 if constexpr( !std::is_same_v<unsigned char, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(unsigned char)) ) {
636 return pc2.setError(__LINE__);
637 }
638 break;
639 case plength_t::h:
640 if constexpr( !std::is_same_v<unsigned short, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(unsigned short)) ) {
641 return pc2.setError(__LINE__);
642 }
643 break;
644 case plength_t::none:
645 if constexpr( !std::is_same_v<unsigned int, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(unsigned int)) ) {
646 return pc2.setError(__LINE__);
647 }
648 break;
649 case plength_t::l:
650 if constexpr( !std::is_same_v<unsigned long, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(unsigned long)) ) {
651 return pc2.setError(__LINE__);
652 }
653 break;
654 case plength_t::ll:
655 if constexpr( !std::is_same_v<unsigned long long, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(unsigned long long)) ) {
656 return pc2.setError(__LINE__);
657 }
658 break;
659 case plength_t::j:
660 if constexpr( !std::is_same_v<uintmax_t, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(uintmax_t)) ) {
661 return pc2.setError(__LINE__);
662 }
663 break;
664 case plength_t::z:
665 if constexpr( !std::is_same_v<size_t, V> && (!std::is_integral_v<V> || sizeof(V) > sizeof(size_t)) ) {
666 return pc2.setError(__LINE__);
667 }
668 break;
669#if 0
670 case plength_t::t:
671 if constexpr( !std::is_same_v<unsigned ptrdiff_t, U> ) {
672 return pc2.setError(__LINE__);
673 }
674 break;
675#endif
676 default:
677 return pc2.setError(__LINE__);
678 }
679 return pc2;
680 }
681
682 static constexpr const PResult parseFloatFmtSpec(const PResult pc, const char /*fmt_literal*/) noexcept {
683 if constexpr( std::is_same_v<no_type_t, T> ) {
684 return pc.setError(__LINE__);
685 }
686 const PResult pc2 = PResult(pc, pc.pos, pc.arg_count+1);
687
688 using U = std::remove_cv_t<T>;
689
690 switch( pc2.length_mod ) {
691 case plength_t::none:
692 case plength_t::l:
693 if constexpr( !std::is_same_v<float, U> &&
694 !std::is_same_v<double, U> ) {
695 return pc2.setError(__LINE__);
696 }
697 break;
698 case plength_t::L:
699 if constexpr( !std::is_same_v<float, U> &&
700 !std::is_same_v<double, U> &&
701 !std::is_same_v<long double, U> ) {
702 } else {
703 return pc2.setError(__LINE__);
704 }
705 break;
706 default:
707 return pc2.setError(__LINE__);
708 }
709 return pc2;
710 }
711 };
712
713 constexpr const PResult checkRec(const PResult ctx) noexcept {
714 return Parser<no_type_t>::parseOne(ctx);
715 }
716
717 template <typename Targ, typename... Tnext>
718 constexpr const PResult checkRec(const PResult ctx) noexcept {
719 if constexpr( 0 < sizeof...(Tnext) ) {
720 return checkRec<Tnext...>( Parser<Targ>::parseOne(ctx) );
721 } else {
723 }
724 }
725
726 } // namespace impl
727
728 /**
729 * Strict type validation of arguments against the format string.
730 *
731 * See @ref jau_cfmt_header for details
732 *
733 * @tparam Targs the argument template type pack to be validated against the format string
734 * @param fmt the snprintf format string
735 * @param args passed arguments, used for template type deduction only
736 * @return true if successfully parsed format and arguments, false otherwise.
737 * @see @ref jau_cfmt_header
738 */
739 template <typename... Targs>
740 constexpr bool check(const std::string_view fmt, const Targs &...) noexcept {
741 return !impl::checkRec<Targs...>( PResult(fmt) ).error();
742 }
743
744 /**
745 * Strict type validation of arguments against the format string.
746 *
747 * See @ref jau_cfmt_header for details
748 *
749 * @tparam Targs the argument template type pack to be validated against the format string
750 * @param fmt the snprintf format string
751 * @return true if successfully parsed format and arguments, false otherwise.
752 * @see @ref jau_cfmt_header
753 */
754 template <typename... Targs>
755 constexpr bool check2(const std::string_view fmt) noexcept {
756 return !impl::checkRec<Targs...>( PResult(fmt) ).error();
757 }
758
759 /**
760 * Strict type validation of arguments against the format string.
761 *
762 * See @ref jau_cfmt_header for details
763 *
764 * @tparam Targs the argument template type pack to be validated against the format string
765 * @param fmt the snprintf format string
766 * @param args passed arguments, used for template type deduction only
767 * @return PContext result object for further inspection.
768 * @see @ref jau_cfmt_header
769 */
770 template <typename... Targs>
771 constexpr const PResult checkR(const std::string_view fmt, const Targs &...) noexcept {
772 return impl::checkRec<Targs...>( PResult(fmt) );
773 }
774
775 /**
776 * Strict type validation of arguments against the format string.
777 *
778 * See @ref jau_cfmt_header for details
779 *
780 * @tparam Targs the argument template type pack to be validated against the format string
781 * @param fmt the snprintf format string
782 * @return PContext result object for further inspection.
783 * @see @ref jau_cfmt_header
784 */
785 template <typename... Targs>
786 constexpr const PResult checkR2(const std::string_view fmt) noexcept {
787 return impl::checkRec<Targs...>( PResult(fmt) );
788 }
789
790 /**@}*/
791
792} // namespace jau::cfmt2
793
794#endif // JAU_STRING_CFMT2_HPP_
static constexpr const PResult parseOne(const PResult pc) noexcept
Parse the given argument against the current conversion specifier of the format string.
constexpr Parser() noexcept=delete
constexpr bool check2(const std::string_view fmt) noexcept
Strict type validation of arguments against the format string.
constexpr const PResult checkR(const std::string_view fmt, const Targs &...) noexcept
Strict type validation of arguments against the format string.
static constexpr bool isDigit(const char c) noexcept
std::ostream & operator<<(std::ostream &out, const PResult &pc)
static constexpr const char * to_string(pstate_t s) noexcept
constexpr const PResult checkR2(const std::string_view fmt) noexcept
Strict type validation of arguments against the format string.
static constexpr bool verbose_error
constexpr const PResult checkRec(const PResult ctx) noexcept
Author: Sven Gothel sgothel@jausoft.com Copyright (c) 2024 Gothel Software e.K.
const std::string_view fmt
constexpr char sym() const noexcept
constexpr PResult(const char(&fmt_)[N]) noexcept
constexpr PResult(const std::string_view fmt_) noexcept
constexpr ssize_t argCount() const noexcept
const plength_t length_mod
constexpr bool error() const noexcept
const ssize_t arg_count
constexpr PResult & operator=(const PResult &x) noexcept=delete
const pstate_t state
constexpr bool hasNext() const noexcept
constexpr PResult(const PResult &pre) noexcept=default
std::string toString() const
constexpr jau::cfmt2::PResult check(const std::string_view fmt, const Targs &...) noexcept