LCOV - code coverage report
Current view: top level - as2js/types - string.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 131 132 99.2 %
Date: 2023-07-29 22:00:24 Functions: 10 10 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2005-2023  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/project/as2js
       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             : 
      19             : // self
      20             : //
      21             : #include    "as2js/string.h"
      22             : 
      23             : #include    "as2js/exception.h"
      24             : 
      25             : 
      26             : // libutf8
      27             : //
      28             : #include    <libutf8/base.h>
      29             : #include    <libutf8/iterator.h>
      30             : #include    <libutf8/libutf8.h>
      31             : 
      32             : 
      33             : // C++
      34             : //
      35             : #include    <limits>
      36             : 
      37             : 
      38             : // last include
      39             : //
      40             : #include    <snapdev/poison.h>
      41             : 
      42             : 
      43             : 
      44             : /** \file
      45             :  * \brief Implementation of string functions.
      46             :  *
      47             :  * We use std::string for our strings. We assume that those strings are
      48             :  * composed of valid UTF-8 characters. We use the libutf8 library to
      49             :  * handle the strings seemlessly.
      50             :  *
      51             :  * The functions found here extend the basic string functionality in link
      52             :  * with AlexScript.
      53             :  */
      54             : 
      55             : 
      56             : namespace as2js
      57             : {
      58             : 
      59             : 
      60             : 
      61             : /** \brief Check validity of the string.
      62             :  *
      63             :  * This function checks all the characters for validity. This is based
      64             :  * on a Unicode piece of code that clearly specifies that a certain
      65             :  * number of characters just cannot be used (i.e. this includes UTF-16
      66             :  * surrogates, and any value larger than 0x10FFFF or negative numbers).
      67             :  *
      68             :  * Note that a null character '\0' is considered valid and part of
      69             :  * the string.
      70             :  *
      71             :  * \param[in] s  The string to be validated.
      72             :  *
      73             :  * \return true if the entire string is considered valid.
      74             :  *
      75             :  * \sa valid_character()
      76             :  */
      77       40493 : bool valid(std::string const & s)
      78             : {
      79     1569351 :     for(libutf8::utf8_iterator it(s); it != s.end(); ++it)
      80             :     {
      81     1549104 :         if(*it == libutf8::NOT_A_CHARACTER)
      82             :         {
      83       20246 :             return false;
      84             :         }
      85             :     }
      86             : 
      87       20247 :     return true;
      88             : }
      89             : 
      90             : 
      91             : /** \brief Check whether a UTF-32 character is considered valid.
      92             :  *
      93             :  * The UTF-32 type is limited in the code points that can be used. This
      94             :  * function returns true if the code point of \p c is considered valid.
      95             :  *
      96             :  * Characters in UTF-32 must be defined between 0 and 0x10FFFF inclusive,
      97             :  * except for code points 0xD800 to 0xDFFF which are used as surrogate
      98             :  * in UTF-16 encoding.
      99             :  *
     100             :  * \param[in] c  The UTF-32 character to be checked.
     101             :  *
     102             :  * \return true if c is considered valid.
     103             :  *
     104             :  * \sa valid()
     105             :  */
     106     1115112 : bool valid_character(char32_t c)
     107             : {
     108     1115112 :     return libutf8::is_valid_unicode(c);
     109             : }
     110             : 
     111             : 
     112             : /** \brief Check whether this string represents a valid integer.
     113             :  *
     114             :  * This function checks the strings to see whether it represents a
     115             :  * valid integer. The function supports decimal and hexadecimal
     116             :  * numbers. Octals are not supported because JavaScript does not
     117             :  * convert numbers that start with a 0 as if these were octal
     118             :  * numbers.
     119             :  *
     120             :  * \li Decimal number: [-+]?[0-9]+
     121             :  * \li Hexadecimal number: [-+]?0[xX][0-9a-fA-F]+
     122             :  *
     123             :  * \note
     124             :  * In strict mode, hexadecimal numbers do not accept a sign.
     125             :  *
     126             :  * \param[in] s  The integer to be checked.
     127             :  * \param[in] strict  Whether we are in strict mode (hexadecimal refuse signs).
     128             :  *
     129             :  * \return true if the string represents an integer.
     130             :  *
     131             :  * \sa is_floating_point()
     132             :  * \sa to_integer()
     133             :  * \sa is_number()
     134             :  */
     135     1531597 : bool is_integer(std::string const & s, bool strict)
     136             : {
     137     1531597 :     char const *f(s.c_str());
     138             : 
     139             :     // sign
     140             :     //
     141     1531597 :     bool const is_signed(*f == '-' || *f == '+');
     142     1531597 :     if(is_signed)
     143             :     {
     144     1064241 :         ++f;
     145             :     }
     146             : 
     147             :     // handle special case of hexadecimal
     148             :     //
     149     1531597 :     if(*f == '0')
     150             :     {
     151      600968 :         ++f;
     152      600968 :         if(*f == 'x' || *f == 'X')
     153             :         {
     154      600909 :             if(f[1] == '\0'
     155      600903 :             || (strict && is_signed))
     156             :             {
     157             :                 // just "0x" or "0X" is not a valid number
     158             :                 //
     159           6 :                 return false;
     160             :             }
     161     3184206 :             for(++f; isxdigit(*f); ++f);
     162      600903 :             return *f == '\0';
     163             :         }
     164             :         // no octal support in strings
     165             :     }
     166             : 
     167             :     // number
     168             :     //
     169     9616029 :     for(; *f >= '0' && *f <= '9'; ++f);
     170             : 
     171      930688 :     return *f == '\0';
     172             : }
     173             : 
     174             : 
     175             : /** \brief Check whether the string represents a valid floating pointer number.
     176             :  *
     177             :  * This function parses the string to see whether it represents a valid
     178             :  * floating pointer number:
     179             :  *
     180             :  * \li a sign
     181             :  * \li an integral part
     182             :  * \li a decimal part
     183             :  * \li a signed exponent
     184             :  *
     185             :  * All the elements are optional, however, to be valid, the number requires
     186             :  * at least an integral part or a decimal part.
     187             :  *
     188             :  * Note that this function returns true if the number is an integer.
     189             :  * However, it will return false for hexadecimal numbers. You may also call
     190             :  * the is_number() function to know if a string represents number whether it
     191             :  * is a decimal number or a floating point number.
     192             :  *
     193             :  * \code
     194             :  * [-+]?([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE]?[-+]?[0-9]+)?
     195             :  * \endcode
     196             :  *
     197             :  * \param[in] s  The string to check.
     198             :  *
     199             :  * \return true if the string represents a floating point number.
     200             :  *
     201             :  * \sa is_integer()
     202             :  * \sa to_floating_point()
     203             :  * \sa is_number()
     204             :  */
     205     1208825 : bool is_floating_point(std::string const & s)
     206             : {
     207     1208825 :     char const * f(s.c_str());
     208             : 
     209             :     // handle special case of an empty string representing 0.0
     210             :     //
     211     1208825 :     if(s.empty())
     212             :     {
     213           1 :         return true;
     214             :     }
     215             : 
     216             :     // sign
     217             :     //
     218     1208824 :     if(*f == '-' || *f == '+')
     219             :     {
     220      714498 :         ++f;
     221             :     }
     222             : 
     223             :     // integral part
     224             :     //
     225     1208824 :     bool const has_integral_part(*f >= '0' && *f <= '9');
     226     1208824 :     if(has_integral_part)
     227             :     {
     228     6496348 :         for(++f; *f >= '0' && *f <= '9'; ++f);
     229             :     }
     230             : 
     231             :     // if '.' check for a decimal part
     232             :     //
     233     1208824 :     bool has_decimal_part(false);
     234     1208824 :     bool const has_period(*f == '.');
     235     1208824 :     if(has_period)
     236             :     {
     237      195399 :         ++f;
     238      195399 :         has_decimal_part = *f >= '0' && *f <= '9';
     239      195399 :         if(has_decimal_part)
     240             :         {
     241      333518 :             for(++f; *f >= '0' && *f <= '9'; ++f);
     242             :         }
     243             :     }
     244             : 
     245     1208824 :     if(has_period)
     246             :     {
     247             :         // if there is a period we must have at least one of the integral
     248             :         // or decimal parts
     249             :         //
     250      195399 :         if(!has_integral_part
     251           2 :         && !has_decimal_part)
     252             :         {
     253           2 :             return false;
     254             :         }
     255             :     }
     256             :     else
     257             :     {
     258             :         // if there is no period, we must have an integral part
     259             :         //
     260     1013425 :         if(!has_integral_part)
     261             :         {
     262          19 :             return false;
     263             :         }
     264             :     }
     265             : 
     266             :     // if 'e' check for an exponent
     267             :     // we can have an exponent whether we have a period or not
     268             :     //
     269     1208803 :     if(*f == 'e' || *f == 'E')
     270             :     {
     271       40932 :         ++f;
     272       40932 :         if(*f == '+' || *f == '-')
     273             :         {
     274             :             // skip the exponent sign
     275             :             //
     276       29617 :             ++f;
     277             :         }
     278       40932 :         if(*f < '0' || *f > '9')
     279             :         {
     280             :             // to be valid, the exponent must include at least one digit
     281             :             //
     282           8 :             return false;
     283             :         }
     284       40930 :         for(++f; *f >= '0' && *f <= '9'; ++f);
     285             :     }
     286             : 
     287     1208795 :     return *f == '\0';
     288             : }
     289             : 
     290             : 
     291             : /** \brief Check whether this string represents a number.
     292             :  *
     293             :  * This function checks whether this string represents a number.
     294             :  * This means it returns true in the following cases:
     295             :  *
     296             :  * \li The string represents a decimal number ([-+]?[0-9]+)
     297             :  * \li The string represents a hexadecimal number ([-+]?0[xX][0-9a-fA-F]+)
     298             :  * \li The string represents a floating point number ([-+]?[0-9]+(\.[0-9]+)?([eE]?[0-9]+)?)
     299             :  *
     300             :  * Unfortunately, JavaScript does not understand "true", "false",
     301             :  * and "null" as numbers (even though isNaN(true), isNaN(false),
     302             :  * and isNaN(null) all return true.)
     303             :  *
     304             :  * \warning
     305             :  * This function calls is_integer() and is_floating_point(). This is because
     306             :  * an integer may be written as hexadecimal and the is_floating_point()
     307             :  * function does not recognize that special case.
     308             :  *
     309             :  * \return true if this string represents a valid number
     310             :  *
     311             :  * \sa is_integer()
     312             :  * \sa is_floating_point()
     313             :  */
     314      510537 : bool is_number(std::string const & s)
     315             : {
     316      510537 :     return is_integer(s) || is_floating_point(s);
     317             : }
     318             : 
     319             : 
     320             : /** \brief Convert a string to an integer number.
     321             :  *
     322             :  * This function verifies that the string represents a valid integer
     323             :  * number, if so, it converts it to such and returns the result.
     324             :  *
     325             :  * If the string does not represent a valid integer, then the function
     326             :  * should return NaN. Unfortunately, there is not NaN integer. Instead
     327             :  * it will return zero (0) or it will raise an exception.
     328             :  *
     329             :  * \note
     330             :  * When used by the lexer, it should always work since the lexer reads
     331             :  * integers with the same expected syntax.
     332             :  *
     333             :  * \exception internal_error
     334             :  * The string is not empty and it does not represent what is considered
     335             :  * a valid JavaScript integer.
     336             :  *
     337             :  * \param[in] s  The string to convert to an integer.
     338             :  *
     339             :  * \return The string converted to an integer.
     340             :  */
     341      510526 : integer::value_type to_integer(std::string const & s)
     342             : {
     343      510526 :     if(s.empty())
     344             :     {
     345           2 :         return integer::value_type();
     346             :     }
     347             : 
     348      510524 :     if(is_integer(s))
     349             :     {
     350             :         // Check whether it is a hexadecimal number, because if so
     351             :         // we use base 16. We want to force the base because we do
     352             :         // not support base 8 which std::stoll() could otherwise
     353             :         // switch to when we have a number that starts with zero.
     354             :         //
     355      500032 :         char const *f(s.c_str());
     356      500032 :         if(*f == '+' || *f == '-')
     357             :         {
     358      349751 :             ++f;
     359             :         }
     360      500032 :         if(f[0] == '0' && (f[1] == 'x' || f[1] == 'X'))
     361             :         {
     362             :             // the strtoll() function supports the sign
     363             :             //
     364      200001 :             return std::stoll(s, nullptr, 16);
     365             :         }
     366      300031 :         return std::stoll(s, nullptr, 10);
     367             :     }
     368             : 
     369             :     // this is invalid
     370             :     //
     371       10492 :     throw internal_error("to_integer(std::string const & s) called with an invalid integer.");
     372             : }
     373             : 
     374             : 
     375             : /** \brief Convert a string to a floating point number.
     376             :  *
     377             :  * This function verifies that the string represents a valid floating
     378             :  * point number, if so, it converts it to such and returns the result.
     379             :  *
     380             :  * If the string does not represent a valid floating point, then the
     381             :  * function returns NaN.
     382             :  *
     383             :  * \warning
     384             :  * On an empty string, this function returns 0.0 and not NaN as expected
     385             :  * in JavaScript.
     386             :  *
     387             :  * \note
     388             :  * When used by the lexer, it should always work since the lexer reads
     389             :  * floating points with the same expected syntax.
     390             :  *
     391             :  * \param[in] s  The string to convert.
     392             :  *
     393             :  * \return The string as a floating point.
     394             :  */
     395      687793 : floating_point::value_type to_floating_point(std::string const & s)
     396             : {
     397      687793 :     if(s.empty())
     398             :     {
     399           9 :         return floating_point::value_type();
     400             :     }
     401             : 
     402      687784 :     if(is_floating_point(s))
     403             :     {
     404      487463 :         return std::stod(s, 0);
     405             :     }
     406             : 
     407      200321 :     return std::numeric_limits<floating_point::value_type>::quiet_NaN();
     408             : }
     409             : 
     410             : 
     411             : /** \brief Check whether the string is considered true.
     412             :  *
     413             :  * A string that is empty is considered false. Any other string is
     414             :  * considered true.
     415             :  *
     416             :  * \return true if the string is not empty.
     417             :  */
     418      510541 : bool is_true(std::string const & s)
     419             : {
     420      510541 :     if(s.empty())
     421             :     {
     422           9 :         return false;
     423             :     }
     424             : 
     425             : // Not too sure where I picked that up, but the documentation clearly says
     426             : // that an empty string is false, anything else is true...
     427             : //    if(is_integer(s))
     428             : //    {
     429             : //        return to_integer(s) != 0;
     430             : //    }
     431             : //    if(is_floating_point(s))
     432             : //    {
     433             : //#pragma GCC diagnostic push
     434             : //#pragma GCC diagnostic ignored "-Wfloat-equal"
     435             : //        return to_floating_point(s) != 0.0;
     436             : //#pragma GCC diagnostic pop
     437             : //    }
     438             : 
     439      510532 :     return true;
     440             : }
     441             : 
     442             : 
     443             : /** \brief Make a simplified copy of the input string.
     444             :  *
     445             :  * This function makes a copy of the input string \p s while removing spaces
     446             :  * from the start, the end, and within the string keep a single space.
     447             :  *
     448             :  * If the string starts with a number, then only the number is kept.
     449             :  *
     450             :  * \note
     451             :  * This function is primarily used to compare a string using the
     452             :  * smart match operator.
     453             :  *
     454             :  * \return The simplified string.
     455             :  */
     456          32 : std::string simplify(std::string const & s)
     457             : {
     458          32 :     std::string result;
     459             : 
     460          32 :     libutf8::utf8_iterator it(s);
     461          71 :     for(; it != s.end(); ++it)
     462             :     {
     463             :         // TBD: should we limit the space check to spaces recognized by EMCAScript?
     464          69 :         if(!iswspace(*it))
     465             :         {
     466          30 :             break;
     467             :         }
     468             :     }
     469             : 
     470             :     // accept a signed number
     471             :     //
     472          32 :     if(it != s.end()
     473          32 :     && (*it == '-' || *it == '+'))
     474             :     {
     475           7 :         result += *it;
     476           7 :         ++it;
     477             :     }
     478             : 
     479          32 :     if(it != s.end())
     480             :     {
     481          30 :         if(*it >= '0' && *it <= '9')
     482             :         {
     483             :             // read the number, ignore the rest
     484             :             //
     485          18 :             result += *it;
     486          38 :             for(++it; it != s.end(); ++it)
     487             :             {
     488          38 :                 if(*it < '0' || *it > '9')
     489             :                 {
     490          18 :                     break;
     491             :                 }
     492          20 :                 result += *it;
     493             :             }
     494          18 :             if(it != s.end()
     495          18 :             && *it == '.')
     496             :             {
     497          16 :                 result += '.';
     498          57 :                 for(++it; it != s.end(); ++it)
     499             :                 {
     500          51 :                     if(*it < '0' || *it > '9')
     501             :                     {
     502          10 :                         break;
     503             :                     }
     504          41 :                     result += *it;
     505             :                 }
     506          16 :                 if(it != s.end()
     507          16 :                 && (*it == 'e' || *it == 'E'))
     508             :                 {
     509           4 :                     std::string e;
     510           4 :                     e += *it;
     511           4 :                     ++it;
     512           4 :                     if(it != s.end()
     513           4 :                     && (*it == '+' || *it == '-'))
     514             :                     {
     515           3 :                         e += *it;
     516           3 :                         ++it;
     517             :                     }
     518           4 :                     if(it != s.end()
     519           4 :                     && *it >= '0'
     520           8 :                     && *it <= '9')
     521             :                     {
     522           4 :                         result += e;
     523           4 :                         result += *it;
     524           6 :                         for(++it; it != s.end(); ++it)
     525             :                         {
     526           6 :                             if(*it < '0' || *it > '9')
     527             :                             {
     528           4 :                                 break;
     529             :                             }
     530           2 :                             result += *it;
     531             :                         }
     532             :                     }
     533           4 :                 }
     534             :             }
     535             :             // ignore anything else
     536             :         }
     537             :         else
     538             :         {
     539             :             // read the string, but simplify the spaces
     540             :             //
     541          12 :             bool found_space(false);
     542          88 :             for(; it != s.end(); ++it)
     543             :             {
     544          76 :                 if(iswspace(*it))
     545             :                 {
     546          25 :                     found_space = true;
     547             :                 }
     548             :                 else
     549             :                 {
     550          51 :                     if(found_space)
     551             :                     {
     552           3 :                         result += ' ';
     553           3 :                         found_space = false;
     554             :                     }
     555          51 :                     result += *it;
     556             :                 }
     557             :             }
     558             :         }
     559             :     }
     560             : 
     561          32 :     if(result.empty())
     562             :     {
     563             :         // make an empty string similar to zero
     564           2 :         result = "0";
     565             :     }
     566             : 
     567          64 :     return result;
     568           0 : }
     569             : 
     570             : 
     571           3 : std::string convert(std::wstring const & str)
     572             : {
     573           3 :     return libutf8::to_u8string(str);
     574             : }
     575             : 
     576             : 
     577             : 
     578             : } // namespace as2js
     579             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.14