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
|