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
|