Update copyright notices for 2011
[jansson.git] / src / variadic.c
1 /*
2  * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
3  * Copyright (c) 2010 Graeme Smecher <graeme.smecher@mail.mcgill.ca>
4  *
5  * Jansson is free software; you can redistribute it and/or modify
6  * it under the terms of the MIT license. See LICENSE for details.
7  */
8
9 #include <stdarg.h>
10 #include <string.h>
11 #include <assert.h>
12
13 #include <jansson.h>
14 #include "jansson_private.h"
15
16 static json_t *json_vnpack(json_error_t *error, ssize_t size, const char * const fmt, va_list *ap)
17 {
18     json_t *root = NULL; /* root object */
19     json_t *obj = NULL;
20
21     /* Scanner variables */
22     const char *tok = fmt;
23     const char *etok;
24     int etok_depth;
25
26     char *key = NULL; /* Current key in an object */
27     char *s;
28
29     int line=1;
30     int column=1;
31
32     /* Skip whitespace at the beginning of the string. */
33     while(size && *tok == ' ') {
34         tok++;
35         size--;
36         column++;
37     }
38
39     if(size <= 0) {
40         jsonp_error_set(error, 1, 1, "Empty format string!");
41         return(NULL);
42     }
43
44     /* tok must contain either a container type, or a length-1 string for a
45      * simple type. */
46     if(*tok == '[')
47         root = json_array();
48     else if(*tok == '{')
49         root = json_object();
50     else
51     {
52         /* Simple object. Permit trailing spaces, otherwise complain. */
53         if((ssize_t)strspn(tok+1, " ") < size-1)
54         {
55             jsonp_error_set(error, 1, 1,
56                     "Expected a single object, got %i", size);
57             return(NULL);
58         }
59
60         switch(*tok)
61         {
62             case 's': /* string */
63                 s = va_arg(*ap, char*);
64                 if(!s)
65                 {
66                     jsonp_error_set(error, 1, 1,
67                               "Refusing to handle a NULL string");
68                     return(NULL);
69                 }
70                 return(json_string(s));
71
72             case 'n': /* null */
73                 return(json_null());
74
75             case 'b': /* boolean */
76                 obj = va_arg(*ap, int) ?
77                     json_true() : json_false();
78                 return(obj);
79
80             case 'i': /* integer */
81                 return(json_integer(va_arg(*ap, int)));
82
83             case 'f': /* double-precision float */
84                 return(json_real(va_arg(*ap, double)));
85
86             case 'O': /* a json_t object; increments refcount */
87                 obj = va_arg(*ap, json_t *);
88                 json_incref(obj);
89                 return(obj);
90
91             case 'o': /* a json_t object; doesn't increment refcount */
92                 obj = va_arg(*ap, json_t *);
93                 return(obj);
94
95             default: /* Whoops! */
96                 jsonp_error_set(error, 1, 1,
97                         "Didn't understand format character '%c'",
98                         *tok);
99                 return(NULL);
100         }
101     }
102
103     /* Move past container opening token */
104     tok++;
105     column++;
106
107     while(tok-fmt < size) {
108         switch(*tok) {
109             case '\n':
110                 line++;
111                 column=0;
112                 break;
113
114             case ' ': /* Whitespace */
115                 break;
116
117             case ',': /* Element spacer */
118                 if(key)
119                 {
120                     jsonp_error_set(error, line, column,
121                               "Expected KEY, got COMMA!");
122                     json_decref(root);
123                     return(NULL);
124                 }
125                 break;
126
127             case ':': /* Key/value separator */
128                 if(!key)
129                 {
130                     jsonp_error_set(error, line, column,
131                               "Got key/value separator without "
132                               "a key preceding it!");
133                     json_decref(root);
134                     return(NULL);
135                 }
136
137                 if(!json_is_object(root))
138                 {
139                     jsonp_error_set(error, line, column,
140                               "Got a key/value separator "
141                               "(':') outside an object!");
142                     json_decref(root);
143                     return(NULL);
144                 }
145
146                 break;
147
148             case ']': /* Close array or object */
149             case '}':
150
151                 if(tok-fmt + (ssize_t)strspn(tok+1, " ") != size-1)
152                 {
153                     jsonp_error_set(error, line, column,
154                               "Unexpected close-bracket '%c'", *tok);
155                     json_decref(root);
156                     return(NULL);
157                 }
158
159                 if((*tok == ']' && !json_is_array(root)) ||
160                    (*tok == '}' && !json_is_object(root)))
161                 {
162                     jsonp_error_set(error, line, column,
163                               "Stray close-array '%c' character", *tok);
164                     json_decref(root);
165                     return(NULL);
166                 }
167                 return(root);
168
169             case '[':
170             case '{':
171
172                 /* Shortcut so we don't mess up the column count in error
173                  * messages */
174                 if(json_is_object(root) && !key)
175                     goto common;
176
177                 /* Find corresponding close bracket */
178                 etok = tok+1;
179                 etok_depth = 1;
180                 while(etok_depth) {
181
182                     if(!*etok || etok-fmt >= size) {
183                         jsonp_error_set(error, line, column,
184                                 "Couldn't find matching close bracket for '%c'",
185                                 *tok);
186                         json_decref(root);
187                         return(NULL);
188                     }
189
190                     if(*tok==*etok)
191                         etok_depth++;
192                     else if(*tok=='[' && *etok==']') {
193                         etok_depth--;
194                         break;
195                     } else if(*tok=='{' && *etok=='}') {
196                         etok_depth--;
197                         break;
198                     }
199
200                     etok++;
201                 }
202
203                 /* Recurse */
204                 obj = json_vnpack(error, etok-tok+1, tok, ap);
205                 if(!obj) {
206                     /* error should already be set */
207                     error->column += column-1;
208                     error->line += line-1;
209                     json_decref(root);
210                     return(NULL);
211                 }
212                 column += etok-tok;
213                 tok = etok;
214                 goto common;
215
216             case 's':
217                 /* Handle strings specially, since they're used for both keys
218                  * and values */
219                 s = va_arg(*ap, char*);
220
221                 if(!s)
222                 {
223                     jsonp_error_set(error, line, column,
224                               "Refusing to handle a NULL string");
225                     json_decref(root);
226                     return(NULL);
227                 }
228
229                 if(json_is_object(root) && !key)
230                 {
231                     /* It's a key */
232                     key = s;
233                     break;
234                 }
235
236                 obj = json_string(s);
237                 goto common;
238
239         default:
240                 obj = json_vnpack(error, 1, tok, ap);
241                 if(!obj) {
242                     json_decref(root);
243                     return(NULL);
244                 }
245
246 common:
247                 /* Add to container */
248                 if(json_is_object(root)) {
249                     if(!key)
250                     {
251                         jsonp_error_set(error, line, column,
252                               "Expected key, got identifier '%c'!", *tok);
253                         json_decref(root);
254                         return(NULL);
255                     }
256
257                     json_object_set_new(root, key, obj);
258                     key = NULL;
259                 }
260                 else
261                 {
262                     json_array_append_new(root, obj);
263                 }
264                 break;
265         }
266         tok++;
267         column++;
268     }
269
270     /* Whoops -- we didn't match the close bracket! */
271     jsonp_error_set(error, line, column, "Missing close array or object!");
272     json_decref(root);
273     return(NULL);
274 }
275
276 static int json_vnunpack(json_t *root, json_error_t *error, ssize_t size, const char *fmt, va_list *ap)
277 {
278
279     int rv=0; /* Return value */
280     int line = 1; /* Line number */
281     int column = 1; /* Column */
282
283     /* Position markers for arrays or objects */
284     int array_index = 0;
285     char *key = NULL;
286
287     const char **s;
288
289     /* Scanner variables */
290     const char *tok = fmt;
291     const char *etok;
292     int etok_depth;
293
294     json_t *obj;
295
296     /* If we're successful, we need to know if the number of arguments
297      * provided matches the number of JSON objects.  We can do this by
298      * counting the elements in every array or object we open up, and
299      * decrementing the count as we visit their children. */
300     int unvisited = 0;
301
302     /* Skip whitespace at the beginning of the string. */
303     while(size && *tok == ' ') {
304         tok++;
305         size--;
306         column++;
307     }
308
309     if(size <= 0) {
310         jsonp_error_set(error, 1, 1, "Empty format string!");
311         return(-2);
312     }
313
314     /* tok must contain either a container type, or a length-1 string for a
315      * simple type. */
316     if(*tok != '[' && *tok != '{')
317     {
318         /* Simple object. Permit trailing spaces, otherwise complain. */
319         if((ssize_t)strspn(tok+1, " ") < size-1)
320         {
321             jsonp_error_set(error, 1, 1,
322                     "Expected a single object, got %i", size);
323             return(-1);
324         }
325
326         switch(*tok)
327         {
328             case 's':
329                 if(!json_is_string(root))
330                 {
331                     jsonp_error_set(error, line, column,
332                             "Type mismatch! Object (%i) wasn't a string.",
333                             json_typeof(root));
334                     return(-2);
335                 }
336                 s = va_arg(*ap, const char **);
337                 if(!s) {
338                     jsonp_error_set(error, line, column, "Passed a NULL string pointer!");
339                     return(-2);
340                 }
341                 *s = json_string_value(root);
342                 return(0);
343
344             case 'i':
345                 if(!json_is_integer(root))
346                 {
347                     jsonp_error_set(error, line, column,
348                             "Type mismatch! Object (%i) wasn't an integer.",
349                             json_typeof(root));
350                     return(-2);
351                 }
352                 *va_arg(*ap, int*) = json_integer_value(root);
353                 return(0);
354
355             case 'b':
356                 if(!json_is_boolean(root))
357                 {
358                     jsonp_error_set(error, line, column,
359                             "Type mismatch! Object (%i) wasn't a boolean.",
360                             json_typeof(root));
361                     return(-2);
362                 }
363                 *va_arg(*ap, int*) = json_is_true(root);
364                 return(0);
365
366             case 'f':
367                 if(!json_is_number(root))
368                 {
369                     jsonp_error_set(error, line, column,
370                             "Type mismatch! Object (%i) wasn't a real.",
371                             json_typeof(root));
372                     return(-2);
373                 }
374                 *va_arg(*ap, double*) = json_number_value(root);
375                 return(0);
376
377             case 'O':
378                 json_incref(root);
379                 /* Fall through */
380
381             case 'o':
382                 *va_arg(*ap, json_t**) = root;
383                 return(0);
384
385             case 'n':
386                 /* Don't actually assign anything; we're just happy
387                  * the null turned up as promised in the format
388                  * string. */
389                 return(0);
390
391             default:
392                 jsonp_error_set(error, line, column,
393                         "Unknown format character '%c'", *tok);
394                 return(-1);
395         }
396     }
397
398     /* Move past container opening token */
399     tok++;
400
401     while(tok-fmt < size) {
402         switch(*tok) {
403             case '\n':
404                 line++;
405                 column=0;
406                 break;
407
408             case ' ': /* Whitespace */
409                 break;
410
411             case ',': /* Element spacer */
412                 if(key)
413                 {
414                     jsonp_error_set(error, line, column,
415                               "Expected KEY, got COMMA!");
416                     return(-2);
417                 }
418                 break;
419
420             case ':': /* Key/value separator */
421                 if(!key)
422                 {
423                     jsonp_error_set(error, line, column,
424                               "Got key/value separator without "
425                               "a key preceding it!");
426                     return(-2);
427                 }
428
429                 if(!json_is_object(root))
430                 {
431                     jsonp_error_set(error, line, column,
432                               "Got a key/value separator "
433                               "(':') outside an object!");
434                     return(-2);
435                 }
436
437                 break;
438
439             case ']': /* Close array or object */
440             case '}':
441
442                 if(tok-fmt + (ssize_t)strspn(tok+1, " ") != size-1)
443                 {
444                     jsonp_error_set(error, line, column,
445                               "Unexpected close-bracket '%c'", *tok);
446                     return(-2);
447                 }
448
449                 if((*tok == ']' && !json_is_array(root)) ||
450                    (*tok == '}' && !json_is_object(root)))
451                 {
452                     jsonp_error_set(error, line, column,
453                               "Stray close-array '%c' character", *tok);
454                     return(-2);
455                 }
456                 return(unvisited);
457
458             case '[':
459             case '{':
460
461                 /* Find corresponding close bracket */
462                 etok = tok+1;
463                 etok_depth = 1;
464                 while(etok_depth) {
465
466                     if(!*etok || etok-fmt >= size) {
467                         jsonp_error_set(error, line, column,
468                                 "Couldn't find matching close bracket for '%c'",
469                                 *tok);
470                         return(-2);
471                     }
472
473                     if(*tok==*etok)
474                         etok_depth++;
475                     else if(*tok=='[' && *etok==']') {
476                         etok_depth--;
477                         break;
478                     } else if(*tok=='{' && *etok=='}') {
479                         etok_depth--;
480                         break;
481                     }
482
483                     etok++;
484                 }
485
486                 /* Recurse */
487                 if(json_is_array(root)) {
488                     rv = json_vnunpack(json_object_get(root, key),
489                             error, etok-tok+1, tok, ap);
490                 } else {
491                     rv = json_vnunpack(json_array_get(root, array_index++),
492                             error, etok-tok+1, tok, ap);
493                 }
494
495                 if(rv < 0) {
496                     /* error should already be set */
497                     error->column += column-1;
498                     error->line += line-1;
499                     return(rv);
500                 }
501
502                 unvisited += rv;
503                 column += etok-tok;
504                 tok = etok;
505                 break;
506
507             case 's':
508                 /* Handle strings specially, since they're used for both keys
509                  * and values */
510
511                 if(json_is_object(root) && !key)
512                 {
513                     /* It's a key */
514                     key = va_arg(*ap, char*);
515
516                     if(!key)
517                     {
518                         jsonp_error_set(error, line, column,
519                                   "Refusing to handle a NULL key");
520                         return(-2);
521                     }
522                     break;
523                 }
524
525                 /* Fall through */
526
527             default:
528
529                 /* Fetch the element from the JSON container */
530                 if(json_is_object(root))
531                     obj = json_object_get(root, key);
532                 else
533                     obj = json_array_get(root, array_index++);
534
535                 if(!obj) {
536                     jsonp_error_set(error, line, column,
537                             "Array/object entry didn't exist!");
538                     return(-1);
539                 }
540
541                 rv = json_vnunpack(obj, error, 1, tok, ap);
542                 if(rv != 0)
543                     return(rv);
544
545                 break;
546         }
547         tok++;
548         column++;
549     }
550
551     /* Whoops -- we didn't match the close bracket! */
552     jsonp_error_set(error, line, column, "Missing close array or object!");
553     return(-2);
554 }
555
556 json_t *json_pack(json_error_t *error, const char *fmt, ...)
557 {
558     va_list ap;
559     json_t *obj;
560
561     jsonp_error_init(error, "");
562
563     if(!fmt || !*fmt) {
564         jsonp_error_set(error, 1, 1, "Null or empty format string!");
565         return(NULL);
566     }
567
568     va_start(ap, fmt);
569     obj = json_vnpack(error, strlen(fmt), fmt, &ap);
570     va_end(ap);
571
572     return(obj);
573 }
574
575 int json_unpack(json_t *root, json_error_t *error, const char *fmt, ...)
576 {
577     va_list ap;
578     int rv;
579
580     jsonp_error_init(error, "");
581
582     if(!fmt || !*fmt) {
583         jsonp_error_set(error, 1, 1, "Null or empty format string!");
584         return(-2);;
585     }
586
587     va_start(ap, fmt);
588     rv = json_vnunpack(root, error, strlen(fmt), fmt, &ap);
589     va_end(ap);
590
591     return(rv);
592 }
593
594 /* vim: ts=4:expandtab:sw=4
595  */