PageRenderTime 680ms CodeModel.GetById 211ms app.highlight 309ms RepoModel.GetById 155ms app.codeStats 0ms

/jansson/src/variadic.c

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