LCOV - code coverage report
Current view: top level - snapdev - tokenize_format.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 369 383 96.3 %
Date: 2023-05-29 16:11:08 Functions: 48 52 92.3 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.14