]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/osrf_parse_json.c
Performance tweak to the logging routines.
[OpenSRF.git] / src / libopensrf / osrf_parse_json.c
1 /*
2 Copyright (C) 2009  Georgia Public Library Service
3 Scott McKellar <scott@esilibrary.com>
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14 */
15
16 /**
17         @file osrf_parse_json.c
18         @brief  Recursive descent parser for JSON.
19 */
20
21 #include <stdlib.h>
22 #include <string.h>
23 #include <stdio.h>
24 #include <ctype.h>
25 #include <opensrf/osrf_json.h>
26 #include <opensrf/osrf_json_utils.h>
27
28 /**
29         @brief A collection of things the parser uses to keep track of what it's doing.
30 */
31 typedef struct {
32         growing_buffer* str_buf;  /**< for building strings */
33         size_t index;             /**< index into input buffer */
34         const char* buff;         /**< client's buffer holding current chunk of input */
35         int decode;               /**< boolean; true if we are decoding class hints */
36 } Parser;
37
38 /**
39         @brief A small buffer for building Unicode byte sequences.
40
41         Because we pass a Unibuff* instead of a bare char*, the receiving function doesn't
42         have to worry about the size of the supplied buffer.  The size is known.
43 */
44 typedef struct {
45         /** @brief A small working buffer.
46
47                 We fill the buffer with four hex characters, and then transform them into a byte
48                 sequence up to three bytes long (plus terminal nul) encoding a UTF-8 character.
49         */
50         unsigned char buff[ 4 ];
51 } Unibuff;
52
53 static jsonObject* parse_it( const char* s, int decode );
54
55 static jsonObject* get_json_node( Parser* parser, char firstc );
56 static const char* get_string( Parser* parser );
57 static jsonObject* get_number( Parser* parser, char firstc );
58 static jsonObject* get_array( Parser* parser );
59 static jsonObject* get_hash( Parser* parser );
60 static jsonObject* get_decoded_hash( Parser* parser );
61 static jsonObject* get_null( Parser* parser );
62 static jsonObject* get_true( Parser* parser );
63 static jsonObject* get_false( Parser* parser );
64 static int get_utf8( Parser* parser, Unibuff* unibuff );
65
66 static char skip_white_space( Parser* parser );
67 static inline void parser_ungetc( Parser* parser );
68 static inline char parser_nextc( Parser* parser );
69 static void report_error( Parser* parser, char badchar, const char* err );
70
71 /* ------------------------------------- */
72
73 /**
74         @brief Parse a JSON string, with decoding of classname hints.
75         @param str Pointer to the JSON string to parse.
76         @return A pointer to the resulting JSON object, or NULL on error.
77
78         If any node in the jsonObject tree is of type JSON_HASH, with a tag of JSON_CLASS_KEY
79         and another tag of JSON_DATA_KEY, the parser will collapse a level.  The subobject
80         tagged with JSON_DATA_KEY will replace the JSON_HASH, and the string tagged as
81         JSON_CLASS_KEY will be stored as its classname.  If there is no tag of JSON_DATA_KEY,
82         the hash will be replaced by a jsonObject of type JSON_NULL.
83
84         The calling code is responsible for freeing the resulting jsonObject.
85 */
86 jsonObject* jsonParse( const char* str ) {
87         return parse_it( str, 1 );
88 }
89
90 /**
91         @brief Parse a JSON string, with no decoding of classname hints.
92         @param s Pointer to the JSON string to parse.
93         @return A pointer to the resulting JSON object, or NULL on error.
94
95         This function is similar to jsonParse(), except that it does not give any special
96         treatment to a JSON_HASH with the JSON_CLASS_KEY tag.
97
98         The calling code is responsible for freeing the resulting jsonObject.
99 */
100 jsonObject* jsonParseRaw( const char* s ) {
101         return parse_it( s, 0 );
102 }
103
104 /**
105         @brief Parse a JSON string received as a printf-style format string.
106         @param str A printf-style format string.  Subsequent arguments, if any, are formatted
107                 and inserted into the JSON string before parsing.
108         @return A pointer to the resulting JSON object, or NULL on error.
109
110         Unlike jsonParse(), this function does not give any special treatment to a JSON_HASH
111         with tags JSON_CLASS_KEY or JSON_DATA_KEY.
112
113         The calling code is responsible for freeing the resulting jsonObject.
114 */
115 jsonObject* jsonParseFmt( const char* str, ... ) {
116         if( !str )
117                 return NULL;
118         VA_LIST_TO_STRING( str );
119         return parse_it( VA_BUF, 0 );
120 }
121
122 /**
123         @brief Parse a JSON string into a jsonObject.
124         @param s Pointer to the string to be parsed.
125         @param decode A boolean; true means decode class hints, false means don't.
126         @return Pointer to the newly created jsonObject.
127
128         Set up a Parser.  Call get_json_node() to do the real work, then make sure that there's
129         nothing but white space at the end.
130 */
131 static jsonObject* parse_it( const char* s, int decode ) {
132
133         if( !s || !*s )
134                 return NULL;    // Nothing to parse
135
136         Parser parser;
137
138         parser.str_buf = NULL;
139         parser.index = 0;
140         parser.buff = s;
141         parser.decode = decode;
142
143         jsonObject* obj = get_json_node( &parser, skip_white_space( &parser ) );
144
145         // Make sure there's nothing but white space at the end
146         char c;
147         if( obj && (c = skip_white_space( &parser )) ) {
148                 report_error( &parser, c, "Extra material follows JSON string" );
149                 jsonObjectFree( obj );
150                 obj = NULL;
151         }
152
153         buffer_free( parser.str_buf );
154         return obj;
155 }
156
157 /**
158         @brief Get the next JSON node -- be it string, number, hash, or whatever.
159         @param parser Pointer to a Parser.
160         @param firstc The first character in the part that we're parsing.
161         @return Pointer to the next JSON node, or NULL upon error.
162
163         The first character tells us what kind of thing we're parsing next: a string, an array,
164         a hash, a number, a boolean, or a null.  Branch accordingly.
165
166         In the case of an array or a hash, this function indirectly calls itself in order to
167         parse subordinate nodes.
168 */
169 static jsonObject* get_json_node( Parser* parser, char firstc ) {
170
171         jsonObject* obj = NULL;
172
173         // Branch on the first character
174         if( '"' == firstc ) {
175                 const char* str = get_string( parser );
176                 if( str ) {
177                         obj = jsonNewObject( NULL );
178                         obj->type = JSON_STRING;
179                         obj->value.s = strdup( str );
180                 }
181         } else if( '[' == firstc ) {
182                 obj = get_array( parser );
183         } else if( '{' == firstc ) {
184                 if( parser->decode )
185                         obj = get_decoded_hash( parser );
186                 else
187                         obj = get_hash( parser );
188         } else if( 'n' == firstc ) {
189                 obj = get_null( parser );
190         } else if( 't' == firstc ) {
191                 obj = get_true( parser );
192         } else if( 'f' == firstc ) {
193                 obj = get_false( parser );
194         }
195         else if( isdigit( (unsigned char) firstc ) ||
196                          '.' == firstc ||
197                          '-' == firstc ||
198                          '+' == firstc ||
199                          'e' == firstc ||
200                          'E' == firstc ) {
201                 obj = get_number( parser, firstc );
202         } else {
203                 report_error( parser, firstc, "Unexpected character" );
204         }
205
206         return obj;
207 }
208
209 /**
210         @brief Collect characters into a character string.
211         @param parser Pointer to a Parser.
212         @return Pointer to parser->str_buf if successful, or NULL upon error.
213
214         Translate the usual escape sequences.  In particular, "\u" escapes a sequence of four
215         hex characters; turn the hex into the corresponding UTF-8 byte sequence.
216
217         Return the string we have built, without the enclosing quotation marks, in
218         parser->str_buf.  In case of error, log an error message.
219 */
220 static const char* get_string( Parser* parser ) {
221
222         if( parser->str_buf )
223                 buffer_reset( parser->str_buf );
224         else
225                 parser->str_buf = buffer_init( 64 );
226
227         growing_buffer* gb = parser->str_buf;
228
229         // Collect the characters.
230         for( ;; ) {
231                 char c = parser_nextc( parser );
232                 if( '"' == c )
233                         break;
234                 else if( !c ) {
235                         report_error( parser, parser->buff[ parser->index - 1  ],
236                                                   "Quoted string not terminated" );
237                         return NULL;
238                 } else if( '\\' == c ) {
239                         c = parser_nextc( parser );
240                         switch( c ) {
241                                 case '"'  : OSRF_BUFFER_ADD_CHAR( gb, '"'  ); break;
242                                 case '\\' : OSRF_BUFFER_ADD_CHAR( gb, '\\' ); break;
243                                 case '/'  : OSRF_BUFFER_ADD_CHAR( gb, '/'  ); break;
244                                 case 'b'  : OSRF_BUFFER_ADD_CHAR( gb, '\b' ); break;
245                                 case 'f'  : OSRF_BUFFER_ADD_CHAR( gb, '\f' ); break;
246                                 case 'n'  : OSRF_BUFFER_ADD_CHAR( gb, '\n' ); break;
247                                 case 'r'  : OSRF_BUFFER_ADD_CHAR( gb, '\r' ); break;
248                                 case 't'  : OSRF_BUFFER_ADD_CHAR( gb, '\t' ); break;
249                                 case 'u'  : {
250                                         Unibuff unibuff;
251                                         if( get_utf8( parser, &unibuff ) ) {
252                                                 return NULL;       // bad UTF-8
253                                         } else if( unibuff.buff[0] ) {
254                                                 OSRF_BUFFER_ADD( gb, (char*) unibuff.buff );
255                                         } else {
256                                                 report_error( parser, 'u', "Unicode sequence encodes a nul byte" );
257                                                 return NULL;
258                                         }
259                                         break;
260                                 }
261                                 default   : OSRF_BUFFER_ADD_CHAR( gb, c );    break;
262                         }
263                 }
264                 else
265                         OSRF_BUFFER_ADD_CHAR( gb, c );
266         }
267
268         return OSRF_BUFFER_C_STR( gb );
269 }
270
271 /**
272         @brief Collect characters into a number, and create a JSON_NUMBER for it.
273         @param parser Pointer to a parser.
274         @param firstc The first character in the number.
275         @return Pointer to a newly created jsonObject of type JSON_NUMBER, or NULL upon error.
276
277         Collect digits, signs, decimal points, and 'E' or 'e' (for scientific notation) into
278         a buffer.  Make sure that the result is numeric.  If it's not numeric by strict JSON
279         rules, try to make it numeric by some judicious massaging (we aren't quite as strict
280         as the official JSON rules).
281
282         If successful, construct a jsonObject of type JSON_NUMBER containing the resulting
283         numeric string.  Otherwise log an error message and return NULL.
284 */
285 static jsonObject* get_number( Parser* parser, char firstc ) {
286
287         if( parser->str_buf )
288                 buffer_reset( parser->str_buf );
289         else
290                 parser->str_buf = buffer_init( 64 );
291
292         growing_buffer* gb = parser->str_buf;
293         OSRF_BUFFER_ADD_CHAR( gb, firstc );
294
295         char c;
296
297         for( ;; ) {
298                 c = parser_nextc( parser );
299                 if( isdigit( (unsigned char) c ) ||
300                         '.' == c ||
301                         '-' == c ||
302                         '+' == c ||
303                         'e' == c ||
304                         'E' == c ) {
305                         OSRF_BUFFER_ADD_CHAR( gb, c );
306                 } else {
307                         if( ! isspace( (unsigned char) c ) )
308                                 parser_ungetc( parser );
309                         break;
310                 }
311         }
312
313         char* s = buffer_data( gb );
314         if( ! jsonIsNumeric( s ) ) {
315                 char* temp = jsonScrubNumber( s );
316                 free( s );
317                 s = temp;
318                 if( !s ) {
319                         report_error( parser, parser->buff[ parser->index - 1 ],
320                                         "Invalid numeric format" );
321                         return NULL;
322                 }
323         }
324
325         jsonObject* obj = jsonNewObject( NULL );
326         obj->type = JSON_NUMBER;
327         obj->value.s = s;
328
329         return obj;
330 }
331
332 /**
333         @brief Parse an array, and create a JSON_ARRAY for it.
334         @param parser Pointer to a Parser.
335         @return Pointer to a newly created jsonObject of type JSON_ARRAY, or NULL upon error.
336
337         Look for a series of JSON nodes, separated by commas and terminated by a right square
338         bracket.  Parse each node recursively, collect them all into a newly created jsonObject
339         of type JSON_ARRAY, and return a pointer to the result.
340
341         Upon error, log an error message and return NULL.
342 */
343 static jsonObject* get_array( Parser* parser ) {
344
345         jsonObject* array = jsonNewObjectType( JSON_ARRAY );
346
347         char c = skip_white_space( parser );
348         if( ']' == c )
349                 return array;          // Empty array
350
351         for( ;; ) {
352                 jsonObject* obj = get_json_node( parser, c );
353                 if( !obj ) {
354                         jsonObjectFree( array );
355                         return NULL;         // Failed to get anything
356                 }
357
358                 // Add the entry to the array
359                 jsonObjectPush( array, obj );
360
361                 // Look for a comma or right bracket
362                 c = skip_white_space( parser );
363                 if( ']' == c )
364                         break;
365                 else if( c != ',' ) {
366                         report_error( parser, c, "Expected comma or bracket in array; didn't find it\n" );
367                         jsonObjectFree( array );
368                         return NULL;
369                 }
370                 c = skip_white_space( parser );
371         }
372
373         return array;
374 }
375
376 /**
377         @brief Parse a hash (JSON object), and create a JSON_HASH for it.
378         @param parser Pointer to a Parser.
379         @return Pointer to a newly created jsonObject of type JSON_HASH, or NULL upon error.
380
381         Look for a series of name/value pairs, separated by commas and terminated by a right
382         curly brace.  Each name/value pair consists of a quoted string, followed by a colon,
383         followed a JSON node of any sort.  Parse the value recursively.
384
385         Collect the name/value pairs into a newly created jsonObject of type JSON_ARRAY, and
386         return a pointer to it.
387
388         Upon error, log an error message and return NULL.
389 */
390 static jsonObject* get_hash( Parser* parser ) {
391         jsonObject* hash = jsonNewObjectType( JSON_HASH );
392
393         char c = skip_white_space( parser );
394         if( '}' == c )
395                 return hash;           // Empty hash
396
397         for( ;; ) {
398
399                 // Get the key string
400                 if( '"' != c ) {
401                         report_error( parser, c,
402                                                   "Expected quotation mark to begin hash key; didn't find it\n" );
403                         jsonObjectFree( hash );
404                         return NULL;
405                 }
406
407                 const char* key = get_string( parser );
408                 if( ! key ) {
409                         jsonObjectFree( hash );
410                         return NULL;
411                 }
412                 char* key_copy = strdup( key );
413
414                 if( jsonObjectGetKey( hash, key_copy ) ) {
415                         report_error( parser, '"', "Duplicate key in JSON object" );
416                         jsonObjectFree( hash );
417                         return NULL;
418                 }
419
420                 // Get the colon
421                 c = skip_white_space( parser );
422                 if( c != ':' ) {
423                         report_error( parser, c,
424                                                   "Expected colon after hash key; didn't find it\n" );
425                         free( key_copy );
426                         jsonObjectFree( hash );
427                         return NULL;
428                 }
429
430                 // Get the associated value
431                 jsonObject* obj = get_json_node( parser, skip_white_space( parser ) );
432                 if( !obj ) {
433                         free( key_copy );
434                         jsonObjectFree( hash );
435                         return NULL;
436                 }
437
438                 // Add a new entry to the hash
439                 jsonObjectSetKey( hash, key_copy, obj );
440                 free( key_copy );
441
442                 // Look for comma or right brace
443                 c = skip_white_space( parser );
444                 if( '}' == c )
445                         break;
446                 else if( c != ',' ) {
447                         report_error( parser, c,
448                                                   "Expected comma or brace in hash, didn't find it" );
449                         jsonObjectFree( hash );
450                         return NULL;
451                 }
452                 c = skip_white_space( parser );
453         }
454
455         return hash;
456 }
457
458 /**
459         @brief Parse a hash (JSON object), and create a JSON_HASH for it; decode class hints.
460         @param parser Pointer to a Parser.
461         @return Pointer to a newly created jsonObject, or NULL upon error.
462
463         This function is similar to get_hash(), @em except:
464
465         If the hash includes a member with a key equal to JSON_CLASS_KEY ("__c" by default),
466         then look for a member whose key is JSON_DATA_KEY ("__p" by default).  If you find one,
467         return the data associated with it; otherwise return a jsonObject of type JSON_NULL.
468
469         If there is no member with a key equal to JSON_CLASS_KEY, then return the same sort of
470         jsonObject as get_hash() would return (except of course that lower levels may be
471         decoded as described above).
472 */
473 static jsonObject* get_decoded_hash( Parser* parser ) {
474         jsonObject* hash = jsonNewObjectType( JSON_HASH );
475
476         char c = skip_white_space( parser );
477         if( '}' == c )
478                 return hash;           // Empty hash
479
480         char* class_name = NULL;
481
482         for( ;; ) {
483
484                 // Get the key string
485                 if( '"' != c ) {
486                         report_error( parser, c,
487                                         "Expected quotation mark to begin hash key; didn't find it\n" );
488                         jsonObjectFree( hash );
489                         return NULL;
490                 }
491
492                 const char* key = get_string( parser );
493                 if( ! key ) {
494                         jsonObjectFree( hash );
495                         return NULL;
496                 }
497                 char* key_copy = strdup( key );
498
499                 if( jsonObjectGetKey( hash, key_copy ) ) {
500                         report_error( parser, '"', "Duplicate key in JSON object" );
501                         jsonObjectFree( hash );
502                         return NULL;
503                 }
504
505                 // Get the colon
506                 c = skip_white_space( parser );
507                 if( c != ':' ) {
508                         report_error( parser, c,
509                                         "Expected colon after hash key; didn't find it\n" );
510                         free( key_copy );
511                         jsonObjectFree( hash );
512                         return NULL;
513                 }
514
515                 // Get the associated value
516                 jsonObject* obj = get_json_node( parser, skip_white_space( parser ) );
517                 if( !obj ) {
518                         free( key_copy );
519                         jsonObjectFree( hash );
520                         return NULL;
521                 }
522
523                 // Add a new entry to the hash
524                 jsonObjectSetKey( hash, key_copy, obj );
525
526                 // Save info for class hint, if present
527                 if( !strcmp( key_copy, JSON_CLASS_KEY ) )
528                         class_name = jsonObjectToSimpleString( obj );
529
530                 free( key_copy );
531
532                 // Look for comma or right brace
533                 c = skip_white_space( parser );
534                 if( '}' == c )
535                         break;
536                 else if( c != ',' ) {
537                         report_error( parser, c,
538                                         "Expected comma or brace in hash, didn't find it" );
539                         jsonObjectFree( hash );
540                         return NULL;
541                 }
542                 c = skip_white_space( parser );
543         }
544
545         if( class_name ) {
546                 // We found a class hint.  Extract the data node and return it.
547                 jsonObject* class_data = osrfHashExtract( hash->value.h, JSON_DATA_KEY );
548                 if( class_data ) {
549                         class_data->parent = NULL;
550                         jsonObjectFree( hash );
551                         hash = class_data;
552                         hash->parent = NULL;
553                         hash->classname = class_name;
554                 } else {
555                         // Huh?  We have a class name but no data for it.
556                         // Throw away what we have and return a JSON_NULL.
557                         jsonObjectFree( hash );
558                         hash = jsonNewObjectType( JSON_NULL );
559                 }
560
561         } else {
562                 if( class_name )
563                         free( class_name );
564         }
565
566         return hash;
567 }
568
569 /**
570         @brief Parse the JSON keyword "null", and create a JSON_NULL for it.
571         @param parser Pointer to a Parser.
572         @return Pointer to a newly created jsonObject of type JSON_NULL, or NULL upon error.
573
574         We already saw an 'n', or we wouldn't be here.  Make sure that the next three characters
575         are 'u', 'l', and 'l', and that the character after that is not a letter or a digit.
576
577         If all goes well, create a jsonObject of type JSON_NULL, and return a pointer to it.
578         Otherwise log an error message and return NULL.
579 */
580 static jsonObject* get_null( Parser* parser ) {
581
582         if( parser_nextc( parser ) != 'u' ||
583                 parser_nextc( parser ) != 'l' ||
584                 parser_nextc( parser ) != 'l' ) {
585                 report_error( parser, parser->buff[ parser->index - 1 ],
586                                 "Expected \"ull\" to follow \"n\"; didn't find it" );
587                 return NULL;
588         }
589
590         // Peek at the next character to make sure that it's kosher
591         char c = parser_nextc( parser );
592         if( ! isspace( (unsigned char) c ) )
593                 parser_ungetc( parser );
594
595         if( isalnum( (unsigned char) c ) ) {
596                 report_error( parser, c, "Found letter or number after \"null\"" );
597                 return NULL;
598         }
599
600         // Everything's okay.  Return a JSON_NULL.
601         return jsonNewObject( NULL );
602 }
603
604 /**
605         @brief Parse the JSON keyword "true", and create a JSON_BOOL for it.
606         @param parser Pointer to a Parser.
607         @return Pointer to a newly created jsonObject of type JSON_BOOL, or NULL upon error.
608
609         We already saw a 't', or we wouldn't be here.  Make sure that the next three characters
610         are 'r', 'u', and 'e', and that the character after that is not a letter or a digit.
611
612         If all goes well, create a jsonObject of type JSON_BOOL, and return a pointer to it.
613         Otherwise log an error message and return NULL.
614 */
615 static jsonObject* get_true( Parser* parser ) {
616
617         if( parser_nextc( parser ) != 'r' ||
618                 parser_nextc( parser ) != 'u' ||
619                 parser_nextc( parser ) != 'e' ) {
620                 report_error( parser, parser->buff[ parser->index - 1 ],
621                                           "Expected \"rue\" to follow \"t\"; didn't find it" );
622                 return NULL;
623         }
624
625         // Peek at the next character to make sure that it's kosher
626         char c = parser_nextc( parser );
627         if( ! isspace( (unsigned char) c ) )
628                 parser_ungetc( parser );
629
630         if( isalnum( (unsigned char) c ) ) {
631                 report_error( parser, c, "Found letter or number after \"true\"" );
632                 return NULL;
633         }
634
635         // Everything's okay.  Return a JSON_BOOL.
636         return jsonNewBoolObject( 1 );
637 }
638
639 /**
640         @brief Parse the JSON keyword "false", and create a JSON_BOOL for it.
641         @param parser Pointer to a Parser.
642         @return Pointer to a newly created jsonObject of type JSON_BOOL, or NULL upon error.
643
644         We already saw a 'f', or we wouldn't be here.  Make sure that the next four characters
645         are 'a', 'l', 's', and 'e', and that the character after that is not a letter or a digit.
646
647         If all goes well, create a jsonObject of type JSON_BOOL, and return a pointer to it.
648         Otherwise log an error message and return NULL.
649 */
650 static jsonObject* get_false( Parser* parser ) {
651
652         if( parser_nextc( parser ) != 'a' ||
653                 parser_nextc( parser ) != 'l' ||
654                 parser_nextc( parser ) != 's' ||
655                 parser_nextc( parser ) != 'e' ) {
656                 report_error( parser, parser->buff[ parser->index - 1 ],
657                                 "Expected \"alse\" to follow \"f\"; didn't find it" );
658                 return NULL;
659         }
660
661         // Peek at the next character to make sure that it's kosher
662         char c = parser_nextc( parser );
663         if( ! isspace( (unsigned char) c ) )
664                 parser_ungetc( parser );
665
666         if( isalnum( (unsigned char) c ) ) {
667                 report_error( parser, c, "Found letter or number after \"false\"" );
668                 return NULL;
669         }
670
671         // Everything's okay.  Return a JSON_BOOL.
672         return jsonNewBoolObject( 0 );
673 }
674
675 /**
676         @brief Convert a hex digit to the corresponding numeric value.
677         @param x A hex digit
678         @return The corresponding numeric value.
679
680         Warning #1: The calling code must ensure that the character to be converted is, in fact,
681         a hex character.  Otherwise the results will be strange.
682
683         Warning #2. This macro evaluates its argument three times.  Beware of side effects.
684         (It might make sense to convert this macro to a static inline function.)
685
686         Warning #3: This code assumes that the characters [a-f] and [A-F] are contiguous in the
687         execution character set, and that the lower 4 bits for 'a' and 'A' are 0001.  Those
688         assumptions are true for ASCII and EBCDIC, but there may be some character sets for
689         which it is not true.
690 */
691 #define hexdigit(x) ( ((x) <= '9') ? (x) - '0' : ((x) & 7) + 9)
692
693 /**
694         @brief Translate the next four characters into a UTF-8 character.
695         @param parser Pointer to a Parser.
696         @param unibuff Pointer to a small buffer in which to return the results.
697         @return 0 if successful, or 1 if not.
698
699         Collect the next four characters into @a unibuff, and make sure that they're all hex.
700         Translate them into a nul-terminated UTF-8 byte sequence, and return the result via
701         @a unibuff.
702
703         (Note that a UTF-8 byte sequence is guaranteed not to contain a nul byte.  Hence using
704         a nul as a terminator creates no ambiguity.)
705 */
706 static int get_utf8( Parser* parser, Unibuff* unibuff ) {
707         char ubuff[ 5 ];
708         int i = 0;
709
710         // Accumulate four characters into a buffer.  Make sure that
711         // there are four of them, and that they're all hex.
712         for( i = 0; i < 4; ++i ) {
713                 int c = parser_nextc( parser );
714                 if( !c ) {
715                         report_error( parser, 'u', "Incomplete Unicode sequence" );
716                         unibuff->buff[ 0 ] = '\0';
717                         return 1;
718                 } else if( ! isxdigit( (unsigned char) c ) ) {
719                         report_error( parser, c, "Non-hex byte found in Unicode sequence" );
720                         unibuff->buff[ 0 ] = '\0';
721                         return 1;
722                 }
723                 else
724                         ubuff[ i ] = c;
725         }
726
727         /* The following code is adapted with permission from
728          * json-c http://oss.metaparadigm.com/json-c/
729          */
730
731         // Convert the hex sequence to a single integer
732         unsigned int ucs_char =
733                         (hexdigit(ubuff[ 0 ]) << 12) +
734                         (hexdigit(ubuff[ 1 ]) <<  8) +
735                         (hexdigit(ubuff[ 2 ]) <<  4) +
736                          hexdigit(ubuff[ 3 ]);
737
738         unsigned char* utf_out = unibuff->buff;
739
740         if (ucs_char < 0x80) {
741                 utf_out[0] = ucs_char;
742                 utf_out[1] = '\0';
743
744         } else if (ucs_char < 0x800) {
745                 utf_out[0] = 0xc0 | (ucs_char >> 6);
746                 utf_out[1] = 0x80 | (ucs_char & 0x3f);
747                 utf_out[2] = '\0';
748
749         } else {
750                 utf_out[0] = 0xe0 | (ucs_char >> 12);
751                 utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f);
752                 utf_out[2] = 0x80 | (ucs_char & 0x3f);
753                 utf_out[3] = '\0';
754         }
755
756         return 0;
757 }
758
759 /**
760         @brief Skip over white space.
761         @param parser Pointer to a Parser.
762         @return The next non-whitespace character.
763 */
764 static char skip_white_space( Parser* parser ) {
765         char c;
766         do {
767                 c = parser_nextc( parser );
768         } while( isspace( (unsigned char) c ) );
769
770         return c;
771 }
772
773 /**
774         @brief Back up by one character.
775         @param parser Pointer to a Parser.
776
777         Decrement an index into the input string.  We don't guard against a negative index, so
778         the calling code should make sure that it doesn't do anything stupid.
779 */
780 static inline void parser_ungetc( Parser* parser ) {
781         --parser->index;
782 }
783
784 /**
785         @brief Get the next character
786         @param parser Pointer to a Parser.
787         @return The next character.
788
789         Increment an index into the input string and return the corresponding character.
790         The calling code should make sure that it doesn't try to read past the terminal nul.
791 */
792 static inline char parser_nextc( Parser* parser ) {
793         return parser->buff[ parser->index++ ];
794 }
795
796 /**
797         @brief Report a syntax error to the log.
798         @param parser Pointer to a Parser.
799         @param badchar The character at the position where the error was detected.
800         @param err Pointer to a descriptive error message.
801
802         Format and log an error message.  Identify the location of the error and
803         the character at that location.  Show the neighborhood of the error within
804         the input string.
805 */
806 static void report_error( Parser* parser, char badchar, const char* err ) {
807
808         // Determine the beginning and ending points of a JSON
809         // fragment to display, from the vicinity of the error
810
811         const int max_margin = 15;  // How many characters to show
812                                     // on either side of the error
813         int pre = parser->index - max_margin;
814         if( pre < 0 )
815                 pre = 0;
816
817         int post = parser->index + 15;
818         if( '\0' == parser->buff[ parser->index ] ) {
819                 post = parser->index - 1;
820         } else {
821                 int remaining = strlen(parser->buff + parser->index);
822                 if( remaining < max_margin )
823                         post = parser->index + remaining;
824         }
825
826         // Copy the fragment into a buffer
827         int len = post - pre + 1;  // length of fragment
828         char buf[len + 1];
829         memcpy( buf, parser->buff + pre, len );
830         buf[ len ] = '\0';
831
832         // Replace newlines and tabs with spaces
833         char* p = buf;
834         while( *p ) {
835                 if( '\n' == *p || '\t' == *p )
836                         *p = ' ';
837                 ++p;
838         }
839
840         // Avoid trying to display a nul character
841         if( '\0' == badchar )
842                 badchar = ' ';
843
844         // Issue the message
845         osrfLogError( OSRF_LOG_MARK,
846                 "*JSON Parser Error\n - char  = %c\n "
847                 "- index = %d\n - near  => %s\n - %s",
848                 badchar, parser->index, buf, err );
849 }