Make json_error_t transparent again
[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 void error_init(json_error_t *error)
17 {
18     if(error)
19     {
20         error->text[0] = '\0';
21         error->line = -1;
22     }
23 }
24
25 static void error_set(json_error_t *error, const int line, const char *msg, ...)
26 {
27     va_list ap;
28
29     if(!error || error->text[0] != '\0') {
30         /* error already set */
31         return;
32     }
33
34     error->line = line;
35
36     va_start(ap, msg);
37     vsnprintf(error->text, JSON_ERROR_TEXT_LENGTH, msg, ap);
38     va_end(ap);
39 }
40
41 json_t *json_pack(json_error_t *error, const char *fmt, ...) {
42     int fmt_length = strlen(fmt);
43     va_list ap;
44
45     /* Keep a stack of containers (lists and objects) */
46     int depth = 0;
47     json_t **stack = NULL;
48
49     /* Keep a list of objects we create in case of error */
50     int free_count = 0;
51     json_t **free_list = NULL;
52
53     json_t *cur = NULL; /* Current container */
54     json_t *root = NULL; /* root object */
55     json_t *obj = NULL;
56
57     char *key = NULL; /* Current key in an object */
58     char *s;
59
60     int line = 1;
61
62     /* Allocation provisioned for worst case */
63     stack = calloc(fmt_length, sizeof(json_t *));
64     free_list = calloc(fmt_length, sizeof(json_t *));
65
66     error_init(error);
67
68     if(!stack || !free_list)
69         goto out;
70
71     va_start(ap, fmt);
72     while(*fmt) {
73         switch(*fmt) {
74             case '\n':
75                 line++;
76                 break;
77
78             case ' ': /* Whitespace */
79                 break;
80
81             case ',': /* Element spacer */
82                 if(!root)
83                 {
84                     error_set(error, line,
85                               "Unexpected COMMA precedes root element!");
86                     root = NULL;
87                     goto out;
88                 }
89
90                 if(!cur)
91                 {
92                     error_set(error, line,
93                               "Unexpected COMMA outside a list or object!");
94                     root = NULL;
95                     goto out;
96                 }
97
98                 if(key)
99                 {
100                     error_set(error, line, "Expected KEY, got COMMA!");
101                     root = NULL;
102                     goto out;
103                 }
104                 break;
105
106             case ':': /* Key/value separator */
107                 if(!key)
108                 {
109                     error_set(error, line, "Got key/value separator without "
110                             "a key preceding it!");
111                     root = NULL;
112                     goto out;
113                 }
114
115                 if(!json_is_object(cur))
116                 {
117                     error_set(error, line, "Got a key/value separator "
118                             "(':') outside an object!");
119                     root = NULL;
120                     goto out;
121                 }
122
123                 break;
124
125             case ']': /* Close array or object */
126             case '}':
127
128                 if(key)
129                 {
130                     error_set(error, line, "OBJECT or ARRAY ended with an "
131                               "incomplete key/value pair!");
132                     root = NULL;
133                     goto out;
134                 }
135
136                 if(depth <= 0)
137                 {
138                     error_set(error, line,
139                               "Too many close-brackets '%c'", *fmt);
140                     root = NULL;
141                     goto out;
142                 }
143
144                 if(*fmt == ']' && !json_is_array(cur))
145                 {
146                     error_set(error, line,
147                               "Stray close-array ']' character");
148                     root = NULL;
149                     goto out;
150                 }
151
152                 if(*fmt == '}' && !json_is_object(cur))
153                 {
154                     error_set(error, line,
155                               "Stray close-object '}' character");
156                     root = NULL;
157                     goto out;
158                 }
159
160                 cur = stack[--depth];
161                 break;
162
163             case '[':
164                 obj = json_array();
165                 goto obj_common;
166
167             case '{':
168                 obj = json_object();
169                 goto obj_common;
170
171             case 's': /* string */
172                 s = va_arg(ap, char*);
173
174                 if(!s)
175                 {
176                     error_set(error, line,
177                               "Refusing to handle a NULL string");
178                     root = NULL;
179                     goto out;
180                 }
181
182                 if(json_is_object(cur) && !key)
183                 {
184                     /* It's a key */
185                     key = s;
186                     break;
187                 }
188
189                 obj = json_string(s);
190                 goto obj_common;
191
192             case 'n': /* null */
193                 obj = json_null();
194                 goto obj_common;
195
196             case 'b': /* boolean */
197                 obj = va_arg(ap, int) ?
198                     json_true() : json_false();
199                 goto obj_common;
200
201             case 'i': /* integer */
202                 obj = json_integer(va_arg(ap, int));
203                 goto obj_common;
204
205             case 'f': /* double-precision float */
206                 obj = json_real(va_arg(ap, double));
207                 goto obj_common;
208
209             case 'O': /* a json_t object; increments refcount */
210                 obj = va_arg(ap, json_t *);
211                 json_incref(obj);
212                 goto obj_common;
213
214             case 'o': /* a json_t object; doesn't increment refcount */
215                 obj = va_arg(ap, json_t *);
216                 goto obj_common;
217
218 obj_common:     free_list[free_count++] = obj;
219
220                 /* Root this object to its parent */
221                 if(json_is_object(cur)) {
222                     if(!key)
223                     {
224                         error_set(error, line,
225                                 "Expected key, got identifier '%c'!", *fmt);
226                         root = NULL;
227                         goto out;
228                     }
229
230                     json_object_set_new(cur, key, obj);
231                     key = NULL;
232                 }
233                 else if(json_is_array(cur))
234                 {
235                     json_array_append_new(cur, obj);
236                 }
237                 else if(!root)
238                 {
239                     printf("Rooting\n");
240                     root = obj;
241                 }
242                 else
243                 {
244                     error_set(error, line, "Can't figure out where to attach "
245                               "'%c' object!", *fmt);
246                     root = NULL;
247                     goto out;
248                 }
249
250                 /* If it was a container ('[' or '{'), descend on the stack */
251                 if(json_is_array(obj) || json_is_object(obj))
252                 {
253                     stack[depth++] = cur;
254                     cur = obj;
255                 }
256
257                 break;
258         }
259         fmt++;
260     }
261     va_end(ap);
262
263     if(depth != 0) {
264         error_set(error, line,
265                 "Missing object or array close-brackets in format string");
266         root = NULL;
267         goto out;
268     }
269
270     /* Success: don't free everything we just built! */
271     free_count = 0;
272
273 out:
274     while(free_count)
275         json_decref(free_list[--free_count]);
276
277     if(free_list)
278         free(free_list);
279
280     if(stack)
281         free(stack);
282
283     return(root);
284 }
285
286 int json_unpack(json_t *root, json_error_t *error, const char *fmt, ...) {
287     va_list ap;
288
289     int rv=0; /* Return value */
290     int line = 1; /* Line number */
291
292     /* Keep a stack of containers (lists and objects) */
293     int depth = 0;
294     json_t **stack;
295
296     int array_index = 0;
297     char *key = NULL; /* Current key in an object */
298
299     json_t *cur = NULL; /* Current container */
300     json_t *obj = NULL;
301
302     int fmt_length = strlen(fmt);
303
304     error_init(error);
305
306     /* Allocation provisioned for worst case */
307     stack = calloc(fmt_length, sizeof(json_t *));
308     if(!stack)
309     {
310         error_set(error, line, "Out of memory!");
311         rv = -1;
312         goto out;
313     }
314
315     /* Even if we're successful, we need to know if the number of
316      * arguments provided matches the number of JSON objects.
317      * We can do this by counting the elements in every array or
318      * object we open up, and decrementing the count as we visit
319      * their children. */
320     int unvisited = 0;
321
322     va_start(ap, fmt);
323     while(*fmt)
324     {
325         switch(*fmt)
326         {
327             case ' ': /* Whitespace */
328                 break;
329
330             case '\n': /* Line break */
331                 line++;
332                 break;
333
334             case ',': /* Element spacer */
335
336                 if(!cur)
337                 {
338                     error_set(error, line,
339                               "Unexpected COMMA outside a list or object!");
340                     rv = -1;
341                     goto out;
342                 }
343
344                 if(key)
345                 {
346                     error_set(error, line, "Expected KEY, got COMMA!");
347                     rv = -1;
348                     goto out;
349                 }
350                 break;
351
352             case ':': /* Key/value separator */
353                 if(!json_is_object(cur) || !key)
354                 {
355                     error_set(error, line, "Unexpected ':'");
356                     rv = -1;
357                     goto out;
358                 }
359                 break;
360
361             case '[':
362             case '{':
363                 /* Fetch object */
364                 if(!cur)
365                 {
366                     obj = root;
367                 }
368                 else if(json_is_object(cur))
369                 {
370                     if(!key)
371                     {
372                         error_set(error, line, "Objects can't be keys");
373                         rv = -1;
374                         goto out;
375                     }
376                     obj = json_object_get(cur, key);
377                     unvisited--;
378                     key = NULL;
379                 }
380                 else if(json_is_array(cur))
381                 {
382                     obj = json_array_get(cur, array_index);
383                     unvisited--;
384                     array_index++;
385                 }
386                 else
387                 {
388                     assert(0);
389                 }
390
391                 /* Make sure we got what we expected */
392                 if(*fmt=='{' && !json_is_object(obj))
393                 {
394                     rv = -2;
395                     goto out;
396                 }
397
398                 if(*fmt=='[' && !json_is_array(obj))
399                 {
400                     rv = -2;
401                     goto out;
402                 }
403
404                 unvisited += json_is_object(obj) ?
405                     json_object_size(obj) :
406                     json_array_size(obj);
407
408                 /* Descend */
409                 stack[depth++] = cur;
410                 cur = obj;
411
412                 key = NULL;
413
414                 break;
415
416
417             case ']':
418             case '}':
419
420                 if(json_is_array(cur) && *fmt!=']')
421                 {
422                     error_set(error, line, "Missing ']'");
423                     rv = -1;
424                     goto out;
425                 }
426
427                 if(json_is_object(cur) && *fmt!='}')
428                 {
429                     error_set(error, line, "Missing '}'");
430                     rv = -1;
431                     goto out;
432                 }
433
434                 if(key)
435                 {
436                     error_set(error, line, "Unexpected '%c'", *fmt);
437                     rv = -1;
438                     goto out;
439                 }
440
441                 if(depth <= 0)
442                 {
443                     error_set(error, line, "Unexpected '%c'", *fmt);
444                     rv = -1;
445                     goto out;
446                 }
447
448                 cur = stack[--depth];
449
450                 break;
451
452             case 's':
453                 if(!key && json_is_object(cur))
454                 {
455                     /* constant string for key */
456                     key = va_arg(ap, char*);
457                     break;
458                 }
459                 /* fall through */
460
461             case 'i': /* integer */
462             case 'f': /* double-precision float */
463             case 'O': /* a json_t object; increments refcount */
464             case 'o': /* a json_t object; borrowed reference */
465             case 'b': /* boolean */
466             case 'n': /* null */
467
468                 /* Fetch object */
469                 if(!cur)
470                 {
471                     obj = root;
472                 }
473                 else if(json_is_object(cur))
474                 {
475                     if(!key)
476                     {
477                         error_set(error, line,
478                                   "Only strings may be used as keys!");
479                         rv = -1;
480                         goto out;
481                     }
482
483                     obj = json_object_get(cur, key);
484                     unvisited--;
485                     key = NULL;
486                 }
487                 else if(json_is_array(cur))
488                 {
489                     obj = json_array_get(cur, array_index);
490                     unvisited--;
491                     array_index++;
492                 }
493                 else
494                 {
495                     error_set(error, line,
496                               "Unsure how to retrieve JSON object '%c'",
497                               *fmt);
498                     rv = -1;
499                     goto out;
500                 }
501
502                 switch(*fmt)
503                 {
504                     case 's':
505                         if(!json_is_string(obj))
506                         {
507                             error_set(error, line,
508                                     "Type mismatch! Object wasn't a string.");
509                             rv = -2;
510                             goto out;
511                         }
512                         *va_arg(ap, const char**) = json_string_value(obj);
513                         break;
514
515                     case 'i':
516                         if(!json_is_integer(obj))
517                         {
518                             error_set(error, line,
519                                     "Type mismatch! Object wasn't an integer.");
520                             rv = -2;
521                             goto out;
522                         }
523                         *va_arg(ap, int*) = json_integer_value(obj);
524                         break;
525
526                     case 'b':
527                         if(!json_is_boolean(obj))
528                         {
529                             error_set(error, line,
530                                     "Type mismatch! Object wasn't a boolean.");
531                             rv = -2;
532                             goto out;
533                         }
534                         *va_arg(ap, int*) = json_is_true(obj);
535                         break;
536
537                     case 'f':
538                         if(!json_is_number(obj))
539                         {
540                             error_set(error, line,
541                                     "Type mismatch! Object wasn't a real.");
542                             rv = -2;
543                             goto out;
544                         }
545                         *va_arg(ap, double*) = json_number_value(obj);
546                         break;
547
548                     case 'O':
549                         json_incref(obj);
550                         /* Fall through */
551
552                     case 'o':
553                         *va_arg(ap, json_t**) = obj;
554                         break;
555
556                     case 'n':
557                         /* Don't actually assign anything; we're just happy
558                          * the null turned up as promised in the format
559                          * string. */
560                         break;
561
562                     default:
563                         error_set(error, line,
564                                 "Unknown format character '%c'", *fmt);
565                         rv = -1;
566                         goto out;
567                 }
568         }
569         fmt++;
570     }
571
572     /* Return 0 if everything was matched; otherwise the number of JSON
573      * objects we didn't get to. */
574     rv = unvisited;
575
576 out:
577     va_end(ap);
578
579     if(stack)
580         free(stack);
581
582     return(rv);
583 }
584
585 /* vim: ts=4:expandtab:sw=4
586  */