Line data Source code
1 : // Copyright (c) 2022-2023 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 : #pragma once
19 :
20 : /** \file
21 : * \brief Tokenize a format string to a list of items.
22 : *
23 : * Various functions make use of the format strings a la `printf(3)`.
24 : * This implements a function that reads the format and generates a
25 : * list of items describing the format using three traits.
26 : *
27 : * The main function supports these traits:
28 : *
29 : * \li The trait defining the format introducer (often `%`) and whether it
30 : * can be doubled ("%%") and/or escaped ("\\\\").
31 : * \li The trait defining the format flags: characters appearing between
32 : * the `%` and the final format letter.
33 : * \li The number trait which defines whether a number can appear between
34 : * the introducer and the format letter.
35 : * \li The trait defining the format letters supported in the string.
36 : *
37 : * This is useful to tokenize the `printf(3)`, `strftime(3)` and other
38 : * similar format strings.
39 : *
40 : * \note
41 : * The classes are templates allowing you to change the type of character
42 : * to any available type (i.e. char, wchar_t, char32_t...). However, multibyte
43 : * characters are not support.
44 : *
45 : * \todo
46 : * Generate errors if order is incorrect (i.e. strftime() expects optional
47 : * flags, optional width, format letter -- any other order will fail in the
48 : * strftime() -- when our format parser allows any order except for the
49 : * format letter which has to be last).
50 : *
51 : * \todo
52 : * Write a generator so we can create a format string by creating a list
53 : * of format_item which we can then transform in '%...<letter>' entries.
54 : *
55 : * \todo
56 : * Add enums for the list of supported formats so we can use that enum
57 : * instead of a plain letter (i.e. 'd' would be PRINTF_FORMAT_DECIMAL,
58 : * for example).
59 : *
60 : * \todo
61 : * Add error messages (at this time, what really happened is weak to say
62 : * the least--although we do not one set of error per format segment).
63 : */
64 :
65 : // self
66 : //
67 : #include <snapdev/not_used.h>
68 :
69 :
70 :
71 : // C++
72 : //
73 : #include <cstdint>
74 : #include <limits>
75 : #include <list>
76 : #include <iostream>
77 : #include <string>
78 : #include <set>
79 :
80 :
81 :
82 : namespace snapdev
83 : {
84 :
85 :
86 :
87 : enum class format_error_t : std::uint8_t
88 : {
89 : FORMAT_ERROR_DUPLICATE, // something is defined more than once
90 : FORMAT_ERROR_EOS, // end of format string found within a format definition
91 : FORMAT_ERROR_MISMATCH, // flag not compatible with format or similar
92 : FORMAT_ERROR_OVERFLOW, // number (width, precision, position) overflow
93 : FORMAT_ERROR_SYNTAX, // something is wrong with the syntax (i.e. '$' without a number in printf format)
94 : FORMAT_ERROR_UNKNOWN, // unknown format character
95 : };
96 :
97 : typedef std::set<format_error_t> format_error_set_t;
98 :
99 :
100 :
101 : typedef std::uint32_t format_flag_t;
102 :
103 : constexpr format_flag_t const FORMAT_FLAG_NONE = 0x000000; // no flags / not a flag
104 :
105 :
106 :
107 : template<typename _CharT>
108 : class format_item
109 : {
110 : public:
111 : typedef _CharT char_t;
112 : typedef std::list<format_item> list_t;
113 :
114 : static constexpr int NUMBER_UNDEFINED = std::numeric_limits<int>::min();
115 :
116 : // errors detected in this format item
117 446 : format_error_set_t const & errors() const
118 : {
119 446 : return f_errors;
120 : }
121 :
122 395 : bool has_errors() const
123 : {
124 395 : return !f_errors.empty();
125 : }
126 :
127 132 : bool has_error(format_error_t e) const
128 : {
129 132 : return f_errors.contains(e);
130 : }
131 :
132 163 : void add_error(format_error_t e)
133 : {
134 163 : f_errors.insert(e);
135 163 : }
136 :
137 : // string segment
138 714 : typename std::basic_string<char_t> string() const
139 : {
140 714 : return f_string;
141 : }
142 :
143 828 : void string(std::basic_string<char_t> const & s)
144 : {
145 828 : f_string = s;
146 828 : }
147 :
148 120 : operator std::string & ()
149 : {
150 120 : return f_string;
151 : }
152 :
153 288 : operator std::string const & () const
154 : {
155 288 : return f_string;
156 : }
157 :
158 : // flags
159 985 : format_flag_t flags() const
160 : {
161 985 : return f_flags;
162 : }
163 :
164 833 : bool has_flags(format_flag_t flags) const
165 : {
166 833 : return (f_flags & flags) != 0;
167 : }
168 :
169 : void flags(format_flag_t flags)
170 : {
171 : f_flags = flags;
172 : }
173 :
174 229 : void add_flags(format_flag_t flags)
175 : {
176 229 : f_flags |= flags;
177 229 : }
178 :
179 32 : void set_masked_flags(format_flag_t flags, format_flag_t mask)
180 : {
181 32 : f_flags = (f_flags & ~mask) | flags;
182 32 : }
183 :
184 : void remove_flags(format_flag_t flags)
185 : {
186 : f_flags &= ~flags;
187 : }
188 :
189 : // width, precision, position
190 313 : int width() const // if negative, we found '*m$'
191 : {
192 313 : return f_width;
193 : }
194 :
195 393 : bool has_width() const
196 : {
197 393 : return f_width != NUMBER_UNDEFINED;
198 : }
199 :
200 30 : void width(int w)
201 : {
202 30 : f_width = w;
203 30 : }
204 :
205 313 : int precision() const // if negative, we found '.*m$'
206 : {
207 313 : return f_precision;
208 : }
209 :
210 393 : bool has_precision() const
211 : {
212 393 : return f_precision != NUMBER_UNDEFINED;
213 : }
214 :
215 21 : void precision(int p)
216 : {
217 21 : f_precision = p;
218 21 : }
219 :
220 313 : int position() const // cannot be negative or 0 if defined
221 : {
222 313 : return f_position;
223 : }
224 :
225 393 : bool has_position() const
226 : {
227 393 : return f_position != NUMBER_UNDEFINED;
228 : }
229 :
230 11 : void position(int position)
231 : {
232 11 : f_position = position;
233 11 : }
234 :
235 : // format
236 622 : char_t format() const
237 : {
238 622 : return f_format;
239 : }
240 :
241 393 : bool is_format() const
242 : {
243 393 : return f_format != static_cast<char_t>('\0');
244 : }
245 :
246 289 : void format(char_t f)
247 : {
248 289 : f_format = f;
249 289 : }
250 :
251 : private:
252 : format_error_set_t f_errors = {};
253 : std::basic_string<char_t> f_string = std::basic_string<char_t>();
254 : format_flag_t f_flags = FORMAT_FLAG_NONE;
255 : int f_width = NUMBER_UNDEFINED;
256 : int f_precision = NUMBER_UNDEFINED;
257 : int f_position = NUMBER_UNDEFINED;
258 : char_t f_format = static_cast<char_t>('\0');
259 : };
260 :
261 :
262 : template<
263 : typename _CharT
264 : , _CharT introducer = '%'
265 : , _CharT start_enclose = '\0'
266 : , _CharT end_enclose = '\0'>
267 : class percent_introducer_traits
268 : {
269 : public:
270 : typedef _CharT char_t;
271 :
272 5641 : static bool is_introducer(char_t c)
273 : {
274 5641 : return c == introducer;
275 : }
276 :
277 : static bool is_start_enclose(char_t c)
278 : {
279 : return start_enclose != '\0' && c == start_enclose;
280 : }
281 :
282 : static bool is_end_enclose(char_t c)
283 : {
284 : return end_enclose != '\0' && c == end_enclose;
285 : }
286 :
287 299 : static bool double_to_escape()
288 : {
289 299 : return true;
290 : }
291 :
292 4034 : static bool escape_character(char_t c)
293 : {
294 4034 : NOT_USED(c);
295 4034 : return false;
296 : }
297 : };
298 :
299 :
300 :
301 : template<typename _CharT>
302 : class no_flag_traits
303 : {
304 : public:
305 : typedef _CharT char_t;
306 :
307 : static bool is_flag(char_t c, format_item<_CharT> & f)
308 : {
309 : return false;
310 : }
311 : };
312 :
313 :
314 :
315 : template<typename _CharT>
316 : class printf_flag_traits
317 : {
318 : public:
319 : typedef _CharT char_t;
320 :
321 : static constexpr format_flag_t const FORMAT_FLAG_ALTERNATE_FORM = 0x0001; // '#'
322 : static constexpr format_flag_t const FORMAT_FLAG_LEFT_ADJUSTED = 0x0002; // '-'
323 : static constexpr format_flag_t const FORMAT_FLAG_SPACE_SIGN = 0x0004; // ' '
324 : static constexpr format_flag_t const FORMAT_FLAG_SHOW_SIGN = 0x0008; // '+'
325 : static constexpr format_flag_t const FORMAT_FLAG_GROUPING = 0x0010; // '\''
326 : static constexpr format_flag_t const FORMAT_FLAG_ALTERNATE_DIGITS = 0x0020; // 'I'
327 :
328 : static constexpr format_flag_t const FORMAT_FLAG_LENGTH_MASK = 0x0F00;
329 : static constexpr format_flag_t const FORMAT_FLAG_LENGTH_INT = 0x0000; // no flag (default)
330 : static constexpr format_flag_t const FORMAT_FLAG_LENGTH_CHAR = 0x0100; // 'hh'
331 : static constexpr format_flag_t const FORMAT_FLAG_LENGTH_SHORT = 0x0200; // 'h'
332 : static constexpr format_flag_t const FORMAT_FLAG_LENGTH_LONG = 0x0300; // 'l'
333 : static constexpr format_flag_t const FORMAT_FLAG_LENGTH_LONG_LONG = 0x0400; // 'll' or 'q'
334 : static constexpr format_flag_t const FORMAT_FLAG_LENGTH_LONG_DOUBLE = 0x0500; // 'L'
335 : static constexpr format_flag_t const FORMAT_FLAG_LENGTH_INTMAX_T = 0x0600; // 'j'
336 : static constexpr format_flag_t const FORMAT_FLAG_LENGTH_SIZE_T = 0x0700; // 'z' (or 'Z')
337 : static constexpr format_flag_t const FORMAT_FLAG_LENGTH_PTRDFF_T = 0x0800; // 't'
338 :
339 534 : static bool is_flag(char_t c, format_item<_CharT> & f)
340 : {
341 534 : switch(c)
342 : {
343 3 : case '#':
344 3 : if(f.has_flags(FORMAT_FLAG_ALTERNATE_FORM))
345 : {
346 1 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
347 : }
348 3 : f.add_flags(FORMAT_FLAG_ALTERNATE_FORM);
349 3 : return true;
350 :
351 3 : case '-':
352 3 : if(f.has_flags(FORMAT_FLAG_LEFT_ADJUSTED))
353 : {
354 1 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
355 : }
356 3 : f.add_flags(FORMAT_FLAG_LEFT_ADJUSTED);
357 3 : return true;
358 :
359 3 : case ' ':
360 3 : if(f.has_flags(FORMAT_FLAG_SPACE_SIGN))
361 : {
362 1 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
363 : }
364 3 : f.add_flags(FORMAT_FLAG_SPACE_SIGN);
365 3 : return true;
366 :
367 3 : case '+':
368 3 : if(f.has_flags(FORMAT_FLAG_SHOW_SIGN))
369 : {
370 1 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
371 : }
372 3 : f.add_flags(FORMAT_FLAG_SHOW_SIGN);
373 3 : return true;
374 :
375 3 : case '\'':
376 3 : if(f.has_flags(FORMAT_FLAG_GROUPING))
377 : {
378 1 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
379 : }
380 3 : f.add_flags(FORMAT_FLAG_GROUPING);
381 3 : return true;
382 :
383 3 : case 'I':
384 3 : if(f.has_flags(FORMAT_FLAG_ALTERNATE_DIGITS))
385 : {
386 1 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
387 : }
388 3 : f.add_flags(FORMAT_FLAG_ALTERNATE_DIGITS);
389 3 : return true;
390 :
391 73 : case 'h':
392 73 : switch(f.flags() & FORMAT_FLAG_LENGTH_MASK)
393 : {
394 28 : case FORMAT_FLAG_LENGTH_INT:
395 28 : f.add_flags(FORMAT_FLAG_LENGTH_SHORT);
396 28 : break;
397 :
398 17 : case FORMAT_FLAG_LENGTH_SHORT:
399 17 : f.set_masked_flags(FORMAT_FLAG_LENGTH_CHAR, FORMAT_FLAG_LENGTH_MASK);
400 17 : break;
401 :
402 28 : default:
403 28 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
404 28 : break;
405 :
406 : }
407 73 : return true;
408 :
409 68 : case 'l':
410 68 : switch(f.flags() & FORMAT_FLAG_LENGTH_MASK)
411 : {
412 25 : case FORMAT_FLAG_LENGTH_INT:
413 25 : f.add_flags(FORMAT_FLAG_LENGTH_LONG);
414 25 : break;
415 :
416 15 : case FORMAT_FLAG_LENGTH_LONG:
417 15 : f.set_masked_flags(FORMAT_FLAG_LENGTH_LONG_LONG, FORMAT_FLAG_LENGTH_MASK);
418 15 : break;
419 :
420 28 : default:
421 28 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
422 28 : break;
423 :
424 : }
425 68 : return true;
426 :
427 23 : case 'q':
428 23 : if((f.flags() & FORMAT_FLAG_LENGTH_MASK) == FORMAT_FLAG_LENGTH_INT)
429 : {
430 13 : f.add_flags(FORMAT_FLAG_LENGTH_LONG_LONG);
431 : }
432 : else
433 : {
434 10 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
435 : }
436 23 : return true;
437 :
438 23 : case 'L':
439 23 : if((f.flags() & FORMAT_FLAG_LENGTH_MASK) == FORMAT_FLAG_LENGTH_INT)
440 : {
441 13 : f.add_flags(FORMAT_FLAG_LENGTH_LONG_DOUBLE);
442 : }
443 : else
444 : {
445 10 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
446 : }
447 23 : return true;
448 :
449 25 : case 'j':
450 25 : if((f.flags() & FORMAT_FLAG_LENGTH_MASK) == FORMAT_FLAG_LENGTH_INT)
451 : {
452 15 : f.add_flags(FORMAT_FLAG_LENGTH_INTMAX_T);
453 : }
454 : else
455 : {
456 10 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
457 : }
458 25 : return true;
459 :
460 47 : case 'z':
461 : case 'Z':
462 47 : if((f.flags() & FORMAT_FLAG_LENGTH_MASK) == FORMAT_FLAG_LENGTH_INT)
463 : {
464 27 : f.add_flags(FORMAT_FLAG_LENGTH_SIZE_T);
465 : }
466 : else
467 : {
468 20 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
469 : }
470 47 : return true;
471 :
472 21 : case 't':
473 21 : if((f.flags() & FORMAT_FLAG_LENGTH_MASK) == FORMAT_FLAG_LENGTH_INT)
474 : {
475 11 : f.add_flags(FORMAT_FLAG_LENGTH_PTRDFF_T);
476 : }
477 : else
478 : {
479 10 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
480 : }
481 21 : return true;
482 :
483 236 : default:
484 236 : return false;
485 :
486 : }
487 : }
488 : };
489 :
490 :
491 :
492 : template<typename _CharT>
493 : class strftime_flag_traits
494 : {
495 : public:
496 : typedef _CharT char_t;
497 :
498 : static constexpr format_flag_t const FORMAT_FLAG_PAD_WITH_SPACES = 0x01; // '_'
499 : static constexpr format_flag_t const FORMAT_FLAG_NO_PAD = 0x02; // '-'
500 : static constexpr format_flag_t const FORMAT_FLAG_PAD_WITH_ZEROES = 0x04; // '0'
501 : static constexpr format_flag_t const FORMAT_FLAG_UPPERCASE = 0x08; // '^'
502 : static constexpr format_flag_t const FORMAT_FLAG_SWAP_CASE = 0x10; // '#'
503 : static constexpr format_flag_t const FORMAT_FLAG_EXTENDED = 0x20; // 'E' -- %Ec, %EC, %EN, %Ex, %EX, %Ey, %EY
504 : static constexpr format_flag_t const FORMAT_FLAG_MODIFIER = 0x10; // 'O' -- %Od, %Oe, %OH, %OI, %Om, %OM, %OS, %Ou, %OU, %OV, %Ow, %OW, %Oy
505 :
506 177 : static bool is_flag(char_t c, format_item<_CharT> & f)
507 : {
508 177 : switch(c)
509 : {
510 9 : case '_':
511 9 : if(f.has_flags(FORMAT_FLAG_PAD_WITH_SPACES))
512 : {
513 1 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
514 : }
515 9 : if(f.has_flags(FORMAT_FLAG_NO_PAD | FORMAT_FLAG_PAD_WITH_ZEROES))
516 : {
517 2 : f.add_error(format_error_t::FORMAT_ERROR_MISMATCH);
518 : }
519 9 : f.add_flags(FORMAT_FLAG_PAD_WITH_SPACES);
520 9 : return true;
521 :
522 9 : case '-':
523 9 : if(f.has_flags(FORMAT_FLAG_NO_PAD))
524 : {
525 1 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
526 : }
527 9 : if(f.has_flags(FORMAT_FLAG_PAD_WITH_SPACES | FORMAT_FLAG_PAD_WITH_ZEROES))
528 : {
529 2 : f.add_error(format_error_t::FORMAT_ERROR_MISMATCH);
530 : }
531 9 : f.add_flags(FORMAT_FLAG_NO_PAD);
532 9 : return true;
533 :
534 10 : case '0':
535 10 : if(f.has_flags(FORMAT_FLAG_PAD_WITH_ZEROES))
536 : {
537 1 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
538 : }
539 10 : if(f.has_flags(FORMAT_FLAG_PAD_WITH_SPACES | FORMAT_FLAG_NO_PAD))
540 : {
541 2 : f.add_error(format_error_t::FORMAT_ERROR_MISMATCH);
542 : }
543 10 : f.add_flags(FORMAT_FLAG_PAD_WITH_ZEROES);
544 10 : return true;
545 :
546 9 : case '^':
547 9 : if(f.has_flags(FORMAT_FLAG_UPPERCASE))
548 : {
549 1 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
550 : }
551 9 : f.add_flags(FORMAT_FLAG_UPPERCASE);
552 9 : return true;
553 :
554 7 : case '#':
555 7 : if(f.has_flags(FORMAT_FLAG_SWAP_CASE))
556 : {
557 1 : f.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
558 : }
559 7 : f.add_flags(FORMAT_FLAG_SWAP_CASE);
560 7 : return true;
561 :
562 133 : default:
563 133 : return false;
564 :
565 : }
566 : }
567 : };
568 :
569 :
570 :
571 : template<typename _CharT>
572 : class no_number_traits
573 : {
574 : public:
575 : typedef _CharT char_t;
576 :
577 30 : static bool support_numbers()
578 : {
579 30 : return false;
580 : }
581 :
582 0 : static bool is_number_separator(char_t c)
583 : {
584 0 : NOT_USED(c);
585 0 : return false;
586 : }
587 :
588 0 : static bool is_number_position(char_t c)
589 : {
590 0 : NOT_USED(c);
591 0 : return false;
592 : }
593 :
594 0 : static bool is_dynamic_position(char_t c)
595 : {
596 0 : NOT_USED(c);
597 0 : return false;
598 : }
599 :
600 0 : static bool parse_number(char_t c, int & number, format_item<_CharT> & f)
601 : {
602 0 : NOT_USED(c, number, f);
603 0 : return false;
604 : }
605 : };
606 :
607 :
608 :
609 : template<typename _CharT>
610 : class printf_number_traits
611 : {
612 : public:
613 : typedef _CharT char_t;
614 :
615 236 : static bool support_numbers()
616 : {
617 236 : return true;
618 : }
619 :
620 236 : static bool is_number_separator(char_t c)
621 : {
622 236 : return c == '.';
623 : }
624 :
625 53 : static bool is_number_position(char_t c)
626 : {
627 53 : return c == '$';
628 : }
629 :
630 218 : static bool is_dynamic_position(char_t c)
631 : {
632 218 : return c == '*';
633 : }
634 :
635 289 : static bool parse_number(char_t c, int & number, format_item<_CharT> & f)
636 : {
637 289 : if(c >= '0' && c <= '9')
638 : {
639 73 : number *= 10;
640 73 : number += c - '0';
641 73 : if(number > 10'000) // let's not exagerate, position or width of more than 10,000?!
642 : {
643 1 : number = 10'000; // prevent further growth
644 1 : f.add_error(format_error_t::FORMAT_ERROR_OVERFLOW);
645 : }
646 73 : return true;
647 : }
648 :
649 216 : return false;
650 : }
651 : };
652 :
653 :
654 :
655 : template<typename _CharT>
656 : class strftime_number_traits
657 : {
658 : public:
659 : typedef _CharT char_t;
660 :
661 105 : static bool support_numbers()
662 : {
663 105 : return true;
664 : }
665 :
666 105 : static bool is_number_separator(char_t c)
667 : {
668 105 : NOT_USED(c);
669 105 : return false;
670 : }
671 :
672 7 : static bool is_number_position(char_t c)
673 : {
674 7 : NOT_USED(c);
675 7 : return false;
676 : }
677 :
678 105 : static bool is_dynamic_position(char_t c)
679 : {
680 105 : NOT_USED(c);
681 105 : return false;
682 : }
683 :
684 127 : static bool parse_number(char_t c, int & number, format_item<_CharT> & f)
685 : {
686 127 : if(c >= '0' && c <= '9')
687 : {
688 22 : number *= 10;
689 22 : number += c - '0';
690 22 : if(number > 10'000)
691 : {
692 5 : number = 10'000; // prevent further growth
693 5 : f.add_error(format_error_t::FORMAT_ERROR_OVERFLOW);
694 : }
695 22 : return true;
696 : }
697 :
698 105 : return false;
699 : }
700 : };
701 :
702 :
703 :
704 : template<typename _CharT>
705 : class printf_letter_traits
706 : {
707 : public:
708 163 : static std::basic_string<_CharT>::size_type is_format(char const * s, format_item<_CharT> & f)
709 : {
710 163 : switch(s[0])
711 : {
712 43 : case 'i': // simplify to 'd'
713 : case 'd':
714 43 : f.format('d');
715 43 : return 1UL;
716 :
717 2 : case 'C': // simplify to 'lc'
718 2 : f.add_flags(printf_flag_traits<_CharT>::FORMAT_FLAG_LENGTH_LONG);
719 2 : f.format('c');
720 2 : return 1UL;
721 :
722 3 : case 'S': // simplify to 'ls'
723 3 : f.add_flags(printf_flag_traits<_CharT>::FORMAT_FLAG_LENGTH_LONG);
724 3 : f.format('s');
725 3 : return 1UL;
726 :
727 113 : case 'o':
728 : case 'u':
729 : case 'x':
730 : case 'X':
731 : case 'e':
732 : case 'E':
733 : case 'f':
734 : case 'F':
735 : case 'g':
736 : case 'G':
737 : case 'a':
738 : case 'A':
739 : case 'c':
740 : case 's':
741 : case 'p':
742 : case 'n':
743 : case 'm':
744 113 : f.format(s[0]);
745 113 : return 1UL;
746 :
747 1 : case '$':
748 : // character is known, but it appears in the wrong place
749 : //
750 1 : f.add_error(format_error_t::FORMAT_ERROR_SYNTAX);
751 1 : break;
752 :
753 1 : default:
754 1 : f.add_error(format_error_t::FORMAT_ERROR_UNKNOWN);
755 1 : break;
756 :
757 : }
758 :
759 2 : return 0;
760 : }
761 : };
762 :
763 :
764 :
765 : template<typename _CharT, bool support_nanoseconds = false>
766 : class strftime_letter_traits
767 : {
768 : public:
769 126 : static std::basic_string<_CharT>::size_type is_format(char const * s, format_item<_CharT> & f)
770 : {
771 126 : switch(s[0])
772 : {
773 4 : case 'h': // equivalent to 'b' so use that instead
774 : case 'b':
775 4 : f.format('b');
776 4 : return 1UL;
777 :
778 7 : case 'N': // Nanoseconds (9 digits, with '0' left padding by default)
779 : if(!support_nanoseconds)
780 : {
781 1 : break;
782 : }
783 : [[fallthrough]];
784 27 : case 'a':
785 : case 'A':
786 : case 'B':
787 : case 'c':
788 : case 'C':
789 : case 'd':
790 : case 'D':
791 : case 'e':
792 : case 'F':
793 : case 'g':
794 : case 'G':
795 : case 'H':
796 : case 'I':
797 : case 'j':
798 : case 'k':
799 : case 'l':
800 : case 'm':
801 : case 'M':
802 : case 'n':
803 : case 'p':
804 : case 'P':
805 : case 'r':
806 : case 'R':
807 : case 's':
808 : case 'S':
809 : case 't':
810 : case 'T':
811 : case 'u':
812 : case 'U':
813 : case 'V':
814 : case 'w':
815 : case 'W':
816 : case 'x':
817 : case 'X':
818 : case 'y':
819 : case 'Y':
820 : case 'z':
821 : case 'Z':
822 : case '+':
823 87 : f.format(s[0]);
824 87 : return 1UL;
825 :
826 16 : case 'E':
827 16 : switch(s[1])
828 : {
829 4 : case 'N': // Nanoseconds without ending zeroes
830 : if(!support_nanoseconds)
831 : {
832 1 : break;
833 : }
834 : [[fallthrough]];
835 1 : case 'c':
836 : case 'C':
837 : case 'x':
838 : case 'X':
839 : case 'y':
840 : case 'Y':
841 14 : f.add_flags(strftime_flag_traits<_CharT>::FORMAT_FLAG_EXTENDED);
842 14 : f.format(s[1]);
843 14 : return 2UL;
844 :
845 : }
846 2 : break;
847 :
848 16 : case 'O':
849 16 : switch(s[1])
850 : {
851 15 : case 'd':
852 : case 'e':
853 : case 'H':
854 : case 'I':
855 : case 'm':
856 : case 'M':
857 : case 'S':
858 : case 'u':
859 : case 'U':
860 : case 'V':
861 : case 'w':
862 : case 'W':
863 : case 'y':
864 : // mark that we found an 'O' modifier followed by a valid letter
865 : //
866 15 : f.add_flags(strftime_flag_traits<_CharT>::FORMAT_FLAG_MODIFIER);
867 15 : f.format(s[1]);
868 15 : return 2UL;
869 :
870 : }
871 1 : break;
872 :
873 : }
874 :
875 6 : f.add_error(format_error_t::FORMAT_ERROR_UNKNOWN);
876 :
877 6 : return 0;
878 : }
879 : };
880 :
881 :
882 :
883 : template<
884 : typename _CharT
885 : , typename LetterTraits
886 : , typename FlagTraits = no_flag_traits<_CharT>
887 : , typename NumberTraits = no_number_traits<_CharT>
888 : , typename IntroducerTraits = percent_introducer_traits<_CharT>>
889 257 : format_item<_CharT>::list_t tokenize_format(std::basic_string<_CharT> const & format_string)
890 : {
891 257 : typename format_item<_CharT>::list_t result;
892 :
893 257 : typename std::basic_string<_CharT>::size_type end(format_string.length());
894 1061 : for(typename std::basic_string<_CharT>::size_type pos(0); pos < end; )
895 : {
896 804 : typename std::basic_string<_CharT>::size_type const begin(pos);
897 804 : if(IntroducerTraits::is_introducer(format_string[pos]))
898 : {
899 299 : format_item<_CharT> item;
900 299 : ++pos;
901 299 : if(IntroducerTraits::double_to_escape()
902 299 : && IntroducerTraits::is_introducer(format_string[pos]))
903 : {
904 : // "%%" case, only save "%" in item string
905 : //
906 4 : item.string(format_string.substr(begin, pos - begin));
907 4 : result.push_back(item);
908 4 : ++pos;
909 : }
910 : else
911 : {
912 : // parse a whole format item
913 : //
914 295 : bool found_width(false);
915 295 : bool found_number_separator(false);
916 295 : bool found_precision(false);
917 295 : bool found_position(false);
918 423 : for(;; ++pos)
919 : {
920 718 : if(pos >= end)
921 : {
922 4 : item.add_error(format_error_t::FORMAT_ERROR_EOS);
923 4 : item.string(format_string.substr(begin, end - begin));
924 4 : result.push_back(item);
925 4 : break;
926 : }
927 :
928 : // handle flags
929 : //
930 714 : if(FlagTraits::is_flag(format_string[pos], item))
931 : {
932 343 : continue;
933 : }
934 :
935 : // handle numbers
936 : //
937 371 : if(NumberTraits::support_numbers())
938 : {
939 341 : if(!found_number_separator)
940 : {
941 302 : if(NumberTraits::is_number_separator(format_string[pos]))
942 : {
943 17 : found_number_separator = true;
944 17 : continue;
945 : }
946 : }
947 : else
948 : {
949 39 : if(NumberTraits::is_number_separator(format_string[pos]))
950 : {
951 : // we cannot have more than one '.' in a valid format
952 : //
953 1 : item.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
954 1 : continue;
955 : }
956 : }
957 323 : bool dynamic_position(NumberTraits::is_dynamic_position(format_string[pos]));
958 323 : if(dynamic_position)
959 : {
960 26 : if(pos + 1 >= end)
961 : {
962 2 : if(found_number_separator)
963 : {
964 1 : item.precision(0);
965 : }
966 : else
967 : {
968 1 : item.width(0);
969 : }
970 2 : continue;
971 : }
972 24 : ++pos;
973 : }
974 321 : bool has_digits(false);
975 321 : int number(0);
976 416 : for(; pos < end; ++pos)
977 : {
978 416 : if(!NumberTraits::parse_number(format_string[pos], number, item))
979 : {
980 321 : break;
981 : }
982 95 : has_digits = true;
983 : }
984 321 : bool const has_number(has_digits || dynamic_position);
985 321 : if(has_number)
986 : {
987 60 : if(NumberTraits::is_number_position(format_string[pos]))
988 : {
989 24 : ++pos;
990 24 : if(dynamic_position)
991 : {
992 13 : if(found_number_separator)
993 : {
994 8 : if(found_precision)
995 : {
996 1 : item.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
997 : }
998 8 : found_precision = true;
999 8 : item.precision(-number);
1000 : }
1001 : else
1002 : {
1003 5 : if(found_width)
1004 : {
1005 1 : item.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
1006 : }
1007 5 : found_width = true;
1008 5 : item.width(-number);
1009 : }
1010 : }
1011 : else
1012 : {
1013 11 : if(found_position)
1014 : {
1015 1 : item.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
1016 : }
1017 11 : found_position = true;
1018 11 : item.position(number);
1019 : }
1020 : }
1021 : else
1022 : {
1023 : // you cannot at the same time have *
1024 : // and a number not followed by a '$'
1025 : //
1026 36 : if(dynamic_position && has_digits)
1027 : {
1028 2 : item.add_error(format_error_t::FORMAT_ERROR_MISMATCH);
1029 : }
1030 36 : if(found_number_separator)
1031 : {
1032 12 : if(found_precision)
1033 : {
1034 3 : item.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
1035 : }
1036 12 : found_precision = true;
1037 12 : item.precision(number);
1038 : }
1039 : else
1040 : {
1041 24 : if(found_width)
1042 : {
1043 3 : item.add_error(format_error_t::FORMAT_ERROR_DUPLICATE);
1044 : }
1045 24 : found_width = true;
1046 24 : item.width(number);
1047 : }
1048 : }
1049 60 : --pos;
1050 60 : continue;
1051 : }
1052 : }
1053 :
1054 : // handle the letter (must match or that format is not valid)
1055 : //
1056 291 : typename std::basic_string<_CharT>::size_type const len(LetterTraits::is_format(format_string.c_str() + pos, item));
1057 291 : if(len != 0)
1058 : {
1059 283 : pos += len;
1060 283 : item.string(format_string.substr(begin, pos - begin));
1061 283 : result.push_back(item);
1062 : }
1063 : else
1064 : {
1065 : // error(s) occurred:
1066 : //
1067 : // 1. save a one character (introducer) item with the error(s)
1068 : // 2. restart just after the introducer
1069 : //
1070 8 : pos = begin + 1;
1071 8 : item.string(format_string.substr(begin, pos - begin));
1072 8 : result.push_back(item);
1073 : }
1074 291 : break;
1075 : }
1076 : }
1077 299 : }
1078 : else
1079 : {
1080 505 : format_item<_CharT> item;
1081 4765 : for(++pos; pos < end; ++pos)
1082 : {
1083 4538 : if(IntroducerTraits::is_introducer(format_string[pos]))
1084 : {
1085 278 : break;
1086 : }
1087 8520 : if(pos + 1 < end
1088 4260 : && IntroducerTraits::escape_character(format_string[pos]))
1089 : {
1090 0 : ++pos;
1091 : }
1092 : }
1093 505 : item.string(format_string.substr(begin, pos - begin));
1094 505 : result.push_back(item);
1095 505 : }
1096 : }
1097 :
1098 257 : return result;
1099 0 : }
1100 :
1101 :
1102 : //template<typename _CharT>
1103 : //std::string join_format_items(
1104 : // std::basic_string<_CharT> const & format_string
1105 : // , typename format_item<_CharT>::list_t items)
1106 : //{
1107 : //}
1108 :
1109 :
1110 :
1111 : } // namespace snapdev
1112 : // vim: ts=4 sw=4 et
|