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