LCOV - code coverage report
Current view: top level - libutf8 - json_tokens.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 267 267 100.0 %
Date: 2022-07-31 10:17:08 Functions: 13 13 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2000-2022  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/project/libutf8
       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 2 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 along
      17             : // with this program; if not, write to the Free Software Foundation, Inc.,
      18             : // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
      19             : 
      20             : /** \file
      21             :  * \brief Implementation of the JSON token parser.
      22             :  *
      23             :  * This file is the implementation of the JSON token parser.
      24             :  *
      25             :  * This is primarily an example of how to use the libutf8 library. It is
      26             :  * pretty easy to parse JSON tokens since there are only a few. This is
      27             :  * expected to be used to read simple structures written using JSON. It
      28             :  * is not expected to be a full JSON parser (see our as2js project for
      29             :  * such).
      30             :  */
      31             : 
      32             : // self
      33             : //
      34             : #include    "libutf8/json_tokens.h"
      35             : 
      36             : #include    "libutf8/exception.h"
      37             : #include    "libutf8/libutf8.h"
      38             : 
      39             : 
      40             : // C++
      41             : //
      42             : #include    <iostream>
      43             : #include    <sstream>
      44             : 
      45             : 
      46             : // last include
      47             : //
      48             : #include    <snapdev/poison.h>
      49             : 
      50             : 
      51             : 
      52             : namespace libutf8
      53             : {
      54             : 
      55             : 
      56             : 
      57             : /** \brief Initialize the JSON tokens object with the specified string.
      58             :  *
      59             :  * At this time, this class is given one string of the entire JSON to be
      60             :  * parsed. The next_token() function gives you the next token until you
      61             :  * get the TOKEN_END which marks the end of the input string.
      62             :  *
      63             :  * \param[in] input  The input to be parsed.
      64             :  */
      65     2225164 : json_tokens::json_tokens(std::string const & input)
      66             :     : f_input(input)
      67     2225164 :     , f_iterator(f_input)
      68             : {
      69     2225164 : }
      70             : 
      71             : 
      72             : /** \brief Get one character.
      73             :  *
      74             :  * This function returns the next character from the input string.
      75             :  *
      76             :  * If the unget() function was used, then those characters are returned first.
      77             :  *
      78             :  * When the end of the input string is reached, the function returns L'\0'
      79             :  * characters.
      80             :  *
      81             :  * \return The next character.
      82             :  */
      83    32988801 : char32_t json_tokens::getc()
      84             : {
      85    32988801 :     if(f_unget_pos > 0)
      86             :     {
      87          19 :         --f_unget_pos;
      88          19 :         return f_unget[f_unget_pos];
      89             :     }
      90             : 
      91    32988782 :     char32_t c(*f_iterator++);
      92             : 
      93    32988782 :     if(c == '\r')
      94             :     {
      95           5 :         c = *f_iterator;
      96           5 :         if(c == '\n')
      97             :         {
      98           3 :             ++f_iterator;
      99             :         }
     100             :         else
     101             :         {
     102           2 :             c = '\n';
     103             :         }
     104             :     }
     105             : 
     106    32988782 :     if(c == '\n')
     107             :     {
     108           9 :         ++f_line;
     109           9 :         f_column = 1;
     110             :     }
     111             :     else
     112             :     {
     113    32988773 :         ++f_column;
     114             :     }
     115             : 
     116    32988782 :     return c;
     117             : }
     118             : 
     119             : 
     120             : /** \brief Restore (unget) one character.
     121             :  *
     122             :  * In many cases, we need to retrieve yet another character to know whether
     123             :  * we reached the end of a token or not (i.e. the end of a number), that
     124             :  * extra character needs to be pushed back as it is not part of the token
     125             :  * but may be part of the next token.
     126             :  *
     127             :  * \note
     128             :  * We don't simply `--f_iterator` because we transform the `"\\r\\n"`
     129             :  * sequence to just `"\\n"` and also increase the `f_line`/`f_column`
     130             :  * accordingly. It could be quite complicated to unget properly in those
     131             :  * cases.
     132             :  *
     133             :  * \param[in] c  The character to push back.
     134             :  */
     135          33 : void json_tokens::ungetc(char32_t c)
     136             : {
     137          33 :     if(f_unget_pos >= std::size(f_unget))
     138             :     {
     139             :         throw libutf8_exception_overflow("too many ungetc() calls, f_unget buffer is full.");  // LCOV_EXCL_LINE
     140             :     }
     141             : 
     142          33 :     f_unget[f_unget_pos] = c;
     143          33 :     ++f_unget_pos;
     144          33 : }
     145             : 
     146             : 
     147             : /** \brief Return the line number.
     148             :  *
     149             :  * This function returns the line number on which the last token was read.
     150             :  *
     151             :  * \return line number of the last token.
     152             :  */
     153        2118 : int json_tokens::line() const
     154             : {
     155        2118 :     return f_last_line;
     156             : }
     157             : 
     158             : 
     159             : /** \brief Return the column number.
     160             :  *
     161             :  * This function returns the column number on which the last token was
     162             :  * read.
     163             :  *
     164             :  * \return column number of the last token.
     165             :  */
     166        2118 : int json_tokens::column() const
     167             : {
     168        2118 :     return f_last_column;
     169             : }
     170             : 
     171             : 
     172             : /** \brief Retrieve the next token.
     173             :  *
     174             :  * This function reads characters and build one token and returns it. If
     175             :  * the end of the input string is reached, then the function returns
     176             :  * the end token (TOKEN_END). Further calls once the end was reached will
     177             :  * always return the TOKEN_END character.
     178             :  *
     179             :  * The sequence of tokens is not handled by the class. You are expected
     180             :  * to create the "yacc". The basics are:
     181             :  *
     182             :  * \code
     183             :  *     start: value
     184             :  *     value: number | string | object | array
     185             :  *     number: TOKEN_NUMBER
     186             :  *     string: TOKEN_STRING
     187             :  *     object: TOKEN_OPEN_OBJECT field_list TOKEN_CLOSE_OBJECT
     188             :  *     array: TOKEN_OPEN_ARRAY item_list TOKEN_CLOSE_ARRAY
     189             :  *     field_list: <empty> | field | field TOKEN_COMMA field_list
     190             :  *     field: string TOKEN_COLON value
     191             :  *     item_list: <empty> | item | item TOKEN_COMMA item_list
     192             :  *     item: value
     193             :  * \endcode
     194             :  *
     195             :  * When the function returns an error (i.e. token_t::TOKEN_ERROR), you can
     196             :  * get the error message using the error() function.
     197             :  *
     198             :  * \return The next token.
     199             :  *
     200             :  * \sa https://www.json.org/
     201             :  */
     202    10009751 : token_t json_tokens::next_token()
     203             : {
     204             :     for(;;)
     205             :     {
     206    10009751 :         f_last_line = f_line;
     207    10009751 :         f_last_column = f_column;
     208    10009751 :         char32_t c(getc());
     209    10009751 :         switch(c)
     210             :         {
     211           2 :         case U'[':
     212           2 :             return token_t::TOKEN_OPEN_ARRAY;
     213             : 
     214           2 :         case U']':
     215           2 :             return token_t::TOKEN_CLOSE_ARRAY;
     216             : 
     217     1112075 :         case U'{':
     218     1112075 :             return token_t::TOKEN_OPEN_OBJECT;
     219             : 
     220     1112068 :         case U'}':
     221     1112068 :             return token_t::TOKEN_CLOSE_OBJECT;
     222             : 
     223          13 :         case U'0':
     224             :         case U'1':
     225             :         case U'2':
     226             :         case U'3':
     227             :         case U'4':
     228             :         case U'5':
     229             :         case U'6':
     230             :         case U'7':
     231             :         case U'8':
     232             :         case U'9':
     233             :         case U'-':
     234             :             {
     235          13 :                 double sign(1.0);
     236          13 :                 if(c == U'-')
     237             :                 {
     238           8 :                     sign = -1.0;
     239           8 :                     c = getc();
     240           8 :                     if(c < U'0' || c > U'9')
     241             :                     {
     242           2 :                         ungetc(c);
     243           2 :                         f_error = "found unexpected character: ";
     244           2 :                         add_error_character(U'-');
     245           2 :                         return token_t::TOKEN_ERROR;
     246             :                     }
     247             :                 }
     248          11 :                 f_number = 0.0;
     249          21 :                 if(c >= U'1' && c <= U'9')
     250             :                 {
     251          10 :                     do
     252             :                     {
     253          20 :                         f_number *= 10.0;
     254          20 :                         f_number += static_cast<double>(c - U'0');
     255          20 :                         c = getc();
     256             :                     }
     257          20 :                     while(c >= U'0' && c <= U'9');
     258             :                 }
     259           1 :                 else if(c != U'0')
     260             :                 {
     261             :                     throw libutf8_logic_exception("somehow c is U'0' when it should not be possible here.");  // LCOV_EXCL_LINE
     262             :                 }
     263             :                 else
     264             :                 {
     265           1 :                     c = getc();
     266             :                 }
     267          11 :                 if(c == U'.')
     268             :                 {
     269           8 :                     constexpr double const one_tenth(1.0 / 10.0);
     270           8 :                     double fraction(1.0);
     271             :                     for(;;)
     272             :                     {
     273          42 :                         c = getc();
     274          25 :                         if(c < U'0' || c > U'9')
     275             :                         {
     276             :                             break;
     277             :                         }
     278          17 :                         fraction *= one_tenth;
     279          17 :                         f_number += (c - U'0') * fraction;
     280             :                     }
     281           8 :                     if(fraction >= 1.0)
     282             :                     {
     283           1 :                         f_error = "number cannot end with a period (\"1.\" is not valid JSON)";
     284           1 :                         return token_t::TOKEN_ERROR;
     285             :                     }
     286             :                 }
     287          10 :                 if(c == U'e' || c == U'E')
     288             :                 {
     289           4 :                     double exponent_sign(1.0);
     290           4 :                     c = getc();
     291           4 :                     if(c == U'+')
     292             :                     {
     293           2 :                         c = getc();
     294             :                     }
     295           2 :                     else if(c == U'-')
     296             :                     {
     297           1 :                         c = getc();
     298           1 :                         exponent_sign = -1.0;
     299             :                     }
     300           4 :                     if(c < U'0' || c > U'9')
     301             :                     {
     302           1 :                         f_error = "number exponent must include at least one digit";
     303           1 :                         return token_t::TOKEN_ERROR;
     304             :                     }
     305           3 :                     double exponent(0.0);
     306          13 :                     while(c >= U'0' && c <= U'9')
     307             :                     {
     308           5 :                         exponent *= 10.0;
     309           5 :                         exponent += c - U'0';
     310           5 :                         c = getc();
     311             :                     }
     312           3 :                     f_number *= pow(10.0, exponent * exponent_sign);
     313             :                 }
     314           9 :                 ungetc(c);
     315           9 :                 f_number *= sign;
     316             :             }
     317           9 :             return token_t::TOKEN_NUMBER;
     318             : 
     319     2225201 :         case U'"':
     320     2225201 :             f_string.clear();
     321             :             for(;;)
     322             :             {
     323    11121921 :                 c = getc();
     324    11121921 :                 if(c == U'"')
     325             :                 {
     326     2224156 :                     break;
     327             :                 }
     328     8897765 :                 if(c == EOS)
     329             :                 {
     330           1 :                     f_error = "unclosed string";
     331           1 :                     return token_t::TOKEN_ERROR;
     332             :                 }
     333     8897764 :                 if(c == U'\\')
     334             :                 {
     335     1113114 :                     c = getc();
     336     1113114 :                     switch(c)
     337             :                     {
     338           3 :                     case U'\\':
     339             :                     case U'"':
     340             :                     case U'/':
     341           3 :                         f_string += c;
     342           3 :                         break;
     343             : 
     344           1 :                     case U'b':
     345           1 :                         f_string += '\b';
     346           1 :                         break;
     347             : 
     348           1 :                     case U'f':
     349           1 :                         f_string += '\f';
     350           1 :                         break;
     351             : 
     352           1 :                     case U'n':
     353           1 :                         f_string += '\n';
     354           1 :                         break;
     355             : 
     356           1 :                     case U'r':
     357           1 :                         f_string += '\r';
     358           1 :                         break;
     359             : 
     360           1 :                     case U't':
     361           1 :                         f_string += '\t';
     362           1 :                         break;
     363             : 
     364     1113104 :                     case U'u':
     365             :                         // this is a UTF-16 character, so we need to
     366             :                         // handle the 0xD800 - 0xDFFF code points
     367             :                         {
     368     1113104 :                             char32_t u(char16(c));
     369     1113104 :                             if(u == EOS)
     370             :                             {
     371           6 :                                 f_error = "invalid unicode character: ";
     372           6 :                                 add_error_character(c);
     373           6 :                                 return token_t::TOKEN_ERROR;
     374             :                             }
     375     1113098 :                             surrogate_t high_surrogate(is_surrogate(u));
     376             : 
     377     1113098 :                             switch(high_surrogate)
     378             :                             {
     379     1048587 :                             case surrogate_t::SURROGATE_HIGH: // UTF-16, must be followed by a low
     380             :                                 {
     381     1048587 :                                     c = getc();
     382     1048587 :                                     if(c != '\\')
     383             :                                     {
     384           1 :                                         f_error = "expected a low surrogate right after a high surrogate, backslash (\\) mising";
     385           1 :                                         return token_t::TOKEN_ERROR;
     386             :                                     }
     387     1048586 :                                     c = getc();
     388     1048586 :                                     if(c != 'u')
     389             :                                     {
     390           1 :                                         f_error = "expected a low surrogate right after a high surrogate, 'u' missing";
     391           1 :                                         return token_t::TOKEN_ERROR;
     392             :                                     }
     393     1048585 :                                     char32_t l(char16(c));
     394     1048585 :                                     if(l == EOS)
     395             :                                     {
     396           6 :                                         f_error = "invalid unicode character: ";
     397           6 :                                         add_error_character(c);
     398           6 :                                         return token_t::TOKEN_ERROR;
     399             :                                     }
     400     1048579 :                                     surrogate_t low_surrogate(is_surrogate(l));
     401     1048579 :                                     if(low_surrogate != surrogate_t::SURROGATE_LOW)
     402             :                                     {
     403           3 :                                         f_error = "expected a low surrogate right after a high surrogate";
     404           3 :                                         return token_t::TOKEN_ERROR;
     405             :                                     }
     406     1048576 :                                     u = ((u & 0x3FF) << 10) + (l & 0x3FF) + 0x10000;
     407             :                                 }
     408     1048576 :                                 break;
     409             : 
     410        1024 :                             case surrogate_t::SURROGATE_LOW:
     411             :                                 {
     412        2048 :                                     std::stringstream ss;
     413        1024 :                                     ss << std::hex << static_cast<int>(u);
     414        1024 :                                     f_error = "low surrogate \\u";
     415        1024 :                                     f_error += ss.str();
     416        2048 :                                     f_error += " found before a high surrogate";
     417             :                                 }
     418        1024 :                                 return token_t::TOKEN_ERROR;
     419             : 
     420       63487 :                             case surrogate_t::SURROGATE_NO:
     421       63487 :                                 break;
     422             : 
     423             :                             }
     424     1112063 :                             f_string += to_u8string(u);
     425             :                         }
     426     1112063 :                         break;
     427             : 
     428           2 :                     default:
     429           2 :                         f_error = "unexpected escape character: ";
     430           2 :                         add_error_character(c);
     431           2 :                         return token_t::TOKEN_ERROR;
     432             : 
     433             :                     }
     434             :                 }
     435     7784650 :                 else if(c == U'\0')
     436             :                 {
     437           1 :                     f_error = "unexpected NULL character in string";
     438           1 :                     return token_t::TOKEN_ERROR;
     439             :                 }
     440             :                 else
     441             :                 {
     442     7784649 :                     f_string += to_u8string(c);
     443             :                 }
     444     8896720 :             }
     445     2224156 :             return token_t::TOKEN_STRING;
     446             : 
     447          17 :         case U',':
     448          17 :             return token_t::TOKEN_COMMA;
     449             : 
     450     1112085 :         case U':':
     451     1112085 :             return token_t::TOKEN_COLON;
     452             : 
     453           4 :         case U't':
     454           4 :             c = getc();
     455           4 :             if(c == U'r')
     456             :             {
     457           3 :                 c = getc();
     458           3 :                 if(c == U'u')
     459             :                 {
     460           2 :                     c = getc();
     461           2 :                     if(c == U'e')
     462             :                     {
     463           1 :                         return token_t::TOKEN_TRUE;
     464             :                     }
     465           1 :                     ungetc(c);
     466           1 :                     ungetc(U'u');
     467             :                 }
     468             :                 else
     469             :                 {
     470           1 :                     ungetc(c);
     471             :                 }
     472           2 :                 ungetc(U'r');
     473             :             }
     474             :             else
     475             :             {
     476           1 :                 ungetc(c);
     477             :             }
     478           3 :             f_error = "found unexpected character: ";
     479           3 :             add_error_character(U't');
     480           3 :             return token_t::TOKEN_ERROR;
     481             : 
     482           5 :         case 'f':
     483           5 :             c = getc();
     484           5 :             if(c == U'a')
     485             :             {
     486           4 :                 c = getc();
     487           4 :                 if(c == U'l')
     488             :                 {
     489           3 :                     c = getc();
     490           3 :                     if(c == U's')
     491             :                     {
     492           2 :                         c = getc();
     493           2 :                         if(c == U'e')
     494             :                         {
     495           1 :                             return token_t::TOKEN_FALSE;
     496             :                         }
     497           1 :                         ungetc(c);
     498           1 :                         ungetc(U's');
     499             :                     }
     500             :                     else
     501             :                     {
     502           1 :                         ungetc(c);
     503             :                     }
     504           2 :                     ungetc(U'l');
     505             :                 }
     506             :                 else
     507             :                 {
     508           1 :                     ungetc(c);
     509             :                 }
     510           3 :                 ungetc(U'a');
     511             :             }
     512             :             else
     513             :             {
     514           1 :                 ungetc(c);
     515             :             }
     516           4 :             f_error = "found unexpected character: ";
     517           4 :             add_error_character(U'f');
     518           4 :             return token_t::TOKEN_ERROR;
     519             : 
     520           4 :         case U'n':
     521           4 :             c = getc();
     522           4 :             if(c == U'u')
     523             :             {
     524           3 :                 c = getc();
     525           3 :                 if(c == U'l')
     526             :                 {
     527           2 :                     c = getc();
     528           2 :                     if(c == U'l')
     529             :                     {
     530           1 :                         return token_t::TOKEN_NULL;
     531             :                     }
     532           1 :                     ungetc(c);
     533           1 :                     ungetc(U'l');
     534             :                 }
     535             :                 else
     536             :                 {
     537           1 :                     ungetc(c);
     538             :                 }
     539           2 :                 ungetc(U'u');
     540             :             }
     541             :             else
     542             :             {
     543           1 :                 ungetc(c);
     544             :             }
     545           3 :             f_error = "found unexpected character: ";
     546           3 :             add_error_character(U'n');
     547           3 :             return token_t::TOKEN_ERROR;
     548             : 
     549          31 :         case U' ':
     550             :         case U'\t':
     551             :         case U'\n':  // this includes the U'\r' (see getc())
     552             :             // ignore blanks between tokens
     553          31 :             break;
     554             : 
     555           1 :         case U'\0':
     556           1 :             f_error = "found unexpected NULL character";
     557           1 :             return token_t::TOKEN_ERROR;
     558             : 
     559     3336198 :         case EOS:
     560     3336198 :             return token_t::TOKEN_END;
     561             : 
     562     1112045 :         default:
     563     1112045 :             f_error = "found unexpected character: ";
     564     1112045 :             add_error_character(c);
     565     1112045 :             return token_t::TOKEN_ERROR;
     566             : 
     567             :         }
     568          31 :     }
     569             : }
     570             : 
     571             : 
     572     2161689 : char32_t json_tokens::char16(char32_t & c)
     573             : {
     574     2161689 :     char32_t u(0);
     575    10808421 :     for(int i(0); i < 4; ++i)
     576             :     {
     577     8646744 :         u <<= 4;
     578     8646744 :         c = getc();
     579     8646744 :         if(c >= U'0' && c <= U'9')
     580             :         {
     581     3307815 :             u += c - U'0';
     582             :         }
     583     5338929 :         else if((c >= U'a' && c <= U'f')
     584          33 :              || (c >= U'A' && c <= U'F'))
     585             :         {
     586     5338917 :             u += (c | 0x20) - (U'a' - 10);
     587             :         }
     588             :         else
     589             :         {
     590          12 :             return EOS;
     591             :         }
     592             :     }
     593     2161677 :     return u;
     594             : }
     595             : 
     596             : 
     597     1112071 : void json_tokens::add_error_character(char32_t c)
     598             : {
     599     1112071 :     f_error += '\'';
     600     1112071 :     if(c < 0x20)
     601             :     {
     602          29 :         f_error += '^';
     603          29 :         f_error += c + 0x40;
     604             :     }
     605     1112042 :     else if(c >= 0x80 && c < 0xA0)
     606             :     {
     607          32 :         f_error += '@';
     608          32 :         f_error += c - 0x40;
     609             :     }
     610     1112010 :     else if(c == EOS)
     611             :     {
     612           6 :         f_error += "EOS";
     613             :     }
     614             :     else
     615             :     {
     616     1112004 :         f_error += to_u8string(c);
     617             :     }
     618     1112071 :     f_error += '\'';
     619     1112071 : }
     620             : 
     621             : 
     622             : /** \brief Return the number token.
     623             :  *
     624             :  * When the last call to the get_token() function returned the
     625             :  * token_t::TOKEN_NUMBER, then the resulting number is saved in
     626             :  * a field which can be retrieved with this function.
     627             :  *
     628             :  * Note that numbers in JSON are always represented by a double.
     629             :  * A double can perfectly hold integers, though.
     630             :  *
     631             :  * \return The last number read by get_token().
     632             :  */
     633           9 : double json_tokens::number() const
     634             : {
     635           9 :     return f_number;
     636             : }
     637             : 
     638             : 
     639             : /** \brief Return the string token.
     640             :  *
     641             :  * When the get_token() function returns the token_t::TOKEN_STRING value,
     642             :  * then the string can be retrieved with this function.
     643             :  *
     644             :  * \return The last string token read.
     645             :  */
     646     2224156 : std::string const & json_tokens::string() const
     647             : {
     648     2224156 :     return f_string;
     649             : }
     650             : 
     651             : 
     652             : /** \brief Return the last error message.
     653             :  *
     654             :  * This function returns the last error message. This represents the
     655             :  * error that last occurred when the next_token() function returned
     656             :  * token_t::TOKEN_ERROR.
     657             :  *
     658             :  * \return The last error message.
     659             :  */
     660     1113105 : std::string const & json_tokens::error() const
     661             : {
     662     1113105 :     return f_error;
     663             : }
     664             : 
     665             : 
     666             : 
     667           6 : } // libutf8 namespace
     668             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13