Make json_pack/json_unpack() recursive
authorGraeme Smecher <graeme.smecher@mail.mcgill.ca>
Fri, 14 Jan 2011 17:18:42 +0000 (09:18 -0800)
committerPetri Lehtinen <petri@digip.org>
Fri, 14 Jan 2011 19:43:48 +0000 (21:43 +0200)
Note that we pass va_list pointers around instead of just va_lists, which
would seem more intuitive. This is necessary since the behaviour of va_lists
passed as function parameters is finicky. Quoth stdarg(3):

If ap is passed to a function that uses va_arg(ap,type) then the value
of ap is undefined after the return of that function.

The pointer-passing strategy is used by Python's Py_BuildValue() for the same
purpose.

src/variadic.c
test/suites/api/test_pack.c
test/suites/api/test_unpack.c

index 89ff1da..aaa0495 100644 (file)
 #include <jansson.h>
 #include "jansson_private.h"
 
-json_t *json_pack(json_error_t *error, const char *fmt, ...) {
-    int fmt_length = strlen(fmt);
-    va_list ap;
-
-    /* Keep a stack of containers (lists and objects) */
-    int depth = 0;
-    json_t **stack = NULL;
-
-    /* Keep a list of objects we create in case of error */
-    int free_count = 0;
-    json_t **free_list = NULL;
-
-    json_t *cur = NULL; /* Current container */
+static json_t *json_vnpack(json_error_t *error, ssize_t size, const char * const fmt, va_list *ap)
+{
     json_t *root = NULL; /* root object */
     json_t *obj = NULL;
 
+    /* Scanner variables */
+    const char *tok = fmt;
+    const char *etok;
+    int etok_depth;
+
     char *key = NULL; /* Current key in an object */
     char *s;
 
-    int line = 1;
+    int line=1;
+    int column=1;
 
-    /* Allocation provisioned for worst case */
-    stack = calloc(fmt_length, sizeof(json_t *));
-    free_list = calloc(fmt_length, sizeof(json_t *));
+    /* Skip whitespace at the beginning of the string. */
+    while(size && *tok == ' ') {
+        tok++;
+        size--;
+        column++;
+    }
 
-    jsonp_error_init(error, "");
+    if(size <= 0) {
+        jsonp_error_set(error, 1, 1, "Empty format string!");
+        return(NULL);
+    }
 
-    if(!stack || !free_list)
-        goto out;
+    /* tok must contain either a container type, or a length-1 string for a
+     * simple type. */
+    if(*tok == '[')
+        root = json_array();
+    else if(*tok == '{')
+        root = json_object();
+    else
+    {
+        /* Simple object. Permit trailing spaces, otherwise complain. */
+        if((ssize_t)strspn(tok+1, " ") < size-1)
+        {
+            jsonp_error_set(error, 1, 1,
+                    "Expected a single object, got %i", size);
+            return(NULL);
+        }
 
-    va_start(ap, fmt);
-    while(*fmt) {
-        switch(*fmt) {
+        switch(*tok)
+        {
+            case 's': /* string */
+                s = va_arg(*ap, char*);
+                if(!s)
+                {
+                    jsonp_error_set(error, 1, 1,
+                              "Refusing to handle a NULL string");
+                    return(NULL);
+                }
+                return(json_string(s));
+
+            case 'n': /* null */
+                return(json_null());
+
+            case 'b': /* boolean */
+                obj = va_arg(*ap, int) ?
+                    json_true() : json_false();
+                return(obj);
+
+            case 'i': /* integer */
+                return(json_integer(va_arg(*ap, int)));
+
+            case 'f': /* double-precision float */
+                return(json_real(va_arg(*ap, double)));
+
+            case 'O': /* a json_t object; increments refcount */
+                obj = va_arg(*ap, json_t *);
+                json_incref(obj);
+                return(obj);
+
+            case 'o': /* a json_t object; doesn't increment refcount */
+                obj = va_arg(*ap, json_t *);
+                return(obj);
+
+            default: /* Whoops! */
+                jsonp_error_set(error, 1, 1,
+                        "Didn't understand format character '%c'",
+                        *tok);
+                return(NULL);
+        }
+    }
+
+    /* Move past container opening token */
+    tok++;
+    column++;
+
+    while(tok-fmt < size) {
+        switch(*tok) {
             case '\n':
                 line++;
+                column=0;
                 break;
 
             case ' ': /* Whitespace */
                 break;
 
             case ',': /* Element spacer */
-                if(!root)
-                {
-                    jsonp_error_set(error, line, -1,
-                              "Unexpected COMMA precedes root element!");
-                    root = NULL;
-                    goto out;
-                }
-
-                if(!cur)
-                {
-                    jsonp_error_set(error, line, -1,
-                              "Unexpected COMMA outside a list or object!");
-                    root = NULL;
-                    goto out;
-                }
-
                 if(key)
                 {
-                    jsonp_error_set(error, line, -1,
+                    jsonp_error_set(error, line, column,
                               "Expected KEY, got COMMA!");
-                    root = NULL;
-                    goto out;
+                    json_decref(root);
+                    return(NULL);
                 }
                 break;
 
             case ':': /* Key/value separator */
                 if(!key)
                 {
-                    jsonp_error_set(error, line, -1,
+                    jsonp_error_set(error, line, column,
                               "Got key/value separator without "
                               "a key preceding it!");
-                    root = NULL;
-                    goto out;
+                    json_decref(root);
+                    return(NULL);
                 }
 
-                if(!json_is_object(cur))
+                if(!json_is_object(root))
                 {
-                    jsonp_error_set(error, line, -1,
+                    jsonp_error_set(error, line, column,
                               "Got a key/value separator "
                               "(':') outside an object!");
-                    root = NULL;
-                    goto out;
+                    json_decref(root);
+                    return(NULL);
                 }
 
                 break;
@@ -103,62 +148,85 @@ json_t *json_pack(json_error_t *error, const char *fmt, ...) {
             case ']': /* Close array or object */
             case '}':
 
-                if(key)
+                if(tok-fmt + (ssize_t)strspn(tok+1, " ") != size-1)
                 {
-                    jsonp_error_set(error, line, -1,
-                              "OBJECT or ARRAY ended with an "
-                              "incomplete key/value pair!");
-                    root = NULL;
-                    goto out;
+                    jsonp_error_set(error, line, column,
+                              "Unexpected close-bracket '%c'", *tok);
+                    json_decref(root);
+                    return(NULL);
                 }
 
-                if(depth <= 0)
+                if((*tok == ']' && !json_is_array(root)) ||
+                   (*tok == '}' && !json_is_object(root)))
                 {
-                    jsonp_error_set(error, line, -1,
-                              "Too many close-brackets '%c'", *fmt);
-                    root = NULL;
-                    goto out;
+                    jsonp_error_set(error, line, column,
+                              "Stray close-array '%c' character", *tok);
+                    json_decref(root);
+                    return(NULL);
                 }
+                return(root);
 
-                if(*fmt == ']' && !json_is_array(cur))
-                {
-                    jsonp_error_set(error, line, -1,
-                              "Stray close-array ']' character");
-                    root = NULL;
-                    goto out;
-                }
+            case '[':
+            case '{':
 
-                if(*fmt == '}' && !json_is_object(cur))
-                {
-                    jsonp_error_set(error, line, -1,
-                              "Stray close-object '}' character");
-                    root = NULL;
-                    goto out;
-                }
+                /* Shortcut so we don't mess up the column count in error
+                 * messages */
+                if(json_is_object(root) && !key)
+                    goto common;
+
+                /* Find corresponding close bracket */
+                etok = tok+1;
+                etok_depth = 1;
+                while(etok_depth) {
+
+                    if(!*etok || etok-fmt >= size) {
+                        jsonp_error_set(error, line, column,
+                                "Couldn't find matching close bracket for '%c'",
+                                *tok);
+                        json_decref(root);
+                        return(NULL);
+                    }
 
-                cur = stack[--depth];
-                break;
+                    if(*tok==*etok)
+                        etok_depth++;
+                    else if(*tok=='[' && *etok==']') {
+                        etok_depth--;
+                        break;
+                    } else if(*tok=='{' && *etok=='}') {
+                        etok_depth--;
+                        break;
+                    }
 
-            case '[':
-                obj = json_array();
-                goto obj_common;
+                    etok++;
+                }
 
-            case '{':
-                obj = json_object();
-                goto obj_common;
+                /* Recurse */
+                obj = json_vnpack(error, etok-tok+1, tok, ap);
+                if(!obj) {
+                    /* error should already be set */
+                    error->column += column-1;
+                    error->line += line-1;
+                    json_decref(root);
+                    return(NULL);
+                }
+                column += etok-tok;
+                tok = etok;
+                goto common;
 
-            case 's': /* string */
-                s = va_arg(ap, char*);
+            case 's':
+                /* Handle strings specially, since they're used for both keys
+                 * and values */
+                s = va_arg(*ap, char*);
 
                 if(!s)
                 {
-                    jsonp_error_set(error, line, -1,
+                    jsonp_error_set(error, line, column,
                               "Refusing to handle a NULL string");
-                    root = NULL;
-                    goto out;
+                    json_decref(root);
+                    return(NULL);
                 }
 
-                if(json_is_object(cur) && !key)
+                if(json_is_object(root) && !key)
                 {
                     /* It's a key */
                     key = s;
@@ -166,400 +234,360 @@ json_t *json_pack(json_error_t *error, const char *fmt, ...) {
                 }
 
                 obj = json_string(s);
-                goto obj_common;
-
-            case 'n': /* null */
-                obj = json_null();
-                goto obj_common;
-
-            case 'b': /* boolean */
-                obj = va_arg(ap, int) ?
-                    json_true() : json_false();
-                goto obj_common;
-
-            case 'i': /* integer */
-                obj = json_integer(va_arg(ap, int));
-                goto obj_common;
-
-            case 'f': /* double-precision float */
-                obj = json_real(va_arg(ap, double));
-                goto obj_common;
-
-            case 'O': /* a json_t object; increments refcount */
-                obj = va_arg(ap, json_t *);
-                json_incref(obj);
-                goto obj_common;
-
-            case 'o': /* a json_t object; doesn't increment refcount */
-                obj = va_arg(ap, json_t *);
-                goto obj_common;
+                goto common;
 
-obj_common:     free_list[free_count++] = obj;
+        default:
+                obj = json_vnpack(error, 1, tok, ap);
+                if(!obj) {
+                    json_decref(root);
+                    return(NULL);
+                }
 
-                /* Root this object to its parent */
-                if(json_is_object(cur)) {
+common:
+                /* Add to container */
+                if(json_is_object(root)) {
                     if(!key)
                     {
-                        jsonp_error_set(error, line, -1,
-                              "Expected key, got identifier '%c'!", *fmt);
-                        root = NULL;
-                        goto out;
+                        jsonp_error_set(error, line, column,
+                              "Expected key, got identifier '%c'!", *tok);
+                        json_decref(root);
+                        return(NULL);
                     }
 
-                    json_object_set_new(cur, key, obj);
+                    json_object_set_new(root, key, obj);
                     key = NULL;
                 }
-                else if(json_is_array(cur))
-                {
-                    json_array_append_new(cur, obj);
-                }
-                else if(!root)
-                {
-                    printf("Rooting\n");
-                    root = obj;
-                }
                 else
                 {
-                    jsonp_error_set(error, line, -1,
-                              "Can't figure out where to attach "
-                              "'%c' object!", *fmt);
-                    root = NULL;
-                    goto out;
+                    json_array_append_new(root, obj);
                 }
-
-                /* If it was a container ('[' or '{'), descend on the stack */
-                if(json_is_array(obj) || json_is_object(obj))
-                {
-                    stack[depth++] = cur;
-                    cur = obj;
-                }
-
                 break;
         }
-        fmt++;
+        tok++;
+        column++;
     }
-    va_end(ap);
 
-    if(depth != 0) {
-        jsonp_error_set(error, line, -1,
-                "Missing object or array close-brackets in format string");
-        root = NULL;
-        goto out;
-    }
-
-    /* Success: don't free everything we just built! */
-    free_count = 0;
-
-out:
-    while(free_count)
-        json_decref(free_list[--free_count]);
-
-    if(free_list)
-        free(free_list);
-
-    if(stack)
-        free(stack);
-
-    return(root);
+    /* Whoops -- we didn't match the close bracket! */
+    jsonp_error_set(error, line, column, "Missing close array or object!");
+    json_decref(root);
+    return(NULL);
 }
 
-int json_unpack(json_t *root, json_error_t *error, const char *fmt, ...) {
-    va_list ap;
+static int json_vnunpack(json_t *root, json_error_t *error, ssize_t size, const char *fmt, va_list *ap)
+{
 
     int rv=0; /* Return value */
     int line = 1; /* Line number */
+    int column = 1; /* Column */
 
-    /* Keep a stack of containers (lists and objects) */
-    int depth = 0;
-    json_t **stack;
-
+    /* Position markers for arrays or objects */
     int array_index = 0;
-    char *key = NULL; /* Current key in an object */
+    char *key = NULL;
 
-    json_t *cur = NULL; /* Current container */
-    json_t *obj = NULL;
+    const char **s;
 
-    int fmt_length = strlen(fmt);
+    /* Scanner variables */
+    const char *tok = fmt;
+    const char *etok;
+    int etok_depth;
 
-    jsonp_error_init(error, "");
+    json_t *obj;
 
-    /* Allocation provisioned for worst case */
-    stack = calloc(fmt_length, sizeof(json_t *));
-    if(!stack)
-    {
-        jsonp_error_set(error, line, -1, "Out of memory!");
-        rv = -1;
-        goto out;
+    /* If we're successful, we need to know if the number of arguments
+     * provided matches the number of JSON objects.  We can do this by
+     * counting the elements in every array or object we open up, and
+     * decrementing the count as we visit their children. */
+    int unvisited = 0;
+
+    /* Skip whitespace at the beginning of the string. */
+    while(size && *tok == ' ') {
+        tok++;
+        size--;
+        column++;
     }
 
-    /* Even if we're successful, we need to know if the number of
-     * arguments provided matches the number of JSON objects.
-     * We can do this by counting the elements in every array or
-     * object we open up, and decrementing the count as we visit
-     * their children. */
-    int unvisited = 0;
+    if(size <= 0) {
+        jsonp_error_set(error, 1, 1, "Empty format string!");
+        return(-2);
+    }
 
-    va_start(ap, fmt);
-    while(*fmt)
+    /* tok must contain either a container type, or a length-1 string for a
+     * simple type. */
+    if(*tok != '[' && *tok != '{')
     {
-        switch(*fmt)
+        /* Simple object. Permit trailing spaces, otherwise complain. */
+        if((ssize_t)strspn(tok+1, " ") < size-1)
         {
-            case ' ': /* Whitespace */
-                break;
-
-            case '\n': /* Line break */
-                line++;
-                break;
-
-            case ',': /* Element spacer */
+            jsonp_error_set(error, 1, 1,
+                    "Expected a single object, got %i", size);
+            return(-1);
+        }
 
-                if(!cur)
+        switch(*tok)
+        {
+            case 's':
+                if(!json_is_string(root))
                 {
-                    jsonp_error_set(error, line, -1,
-                              "Unexpected COMMA outside a list or object!");
-                    rv = -1;
-                    goto out;
+                    jsonp_error_set(error, line, column,
+                            "Type mismatch! Object (%i) wasn't a string.",
+                            json_typeof(root));
+                    return(-2);
                 }
-
-                if(key)
-                {
-                    jsonp_error_set(error, line, -1,
-                              "Expected KEY, got COMMA!");
-                    rv = -1;
-                    goto out;
+                s = va_arg(*ap, const char **);
+                if(!s) {
+                    jsonp_error_set(error, line, column, "Passed a NULL string pointer!");
+                    return(-2);
                 }
-                break;
+                *s = json_string_value(root);
+                return(0);
 
-            case ':': /* Key/value separator */
-                if(!json_is_object(cur) || !key)
+            case 'i':
+                if(!json_is_integer(root))
                 {
-                    jsonp_error_set(error, line, -1, "Unexpected ':'");
-                    rv = -1;
-                    goto out;
+                    jsonp_error_set(error, line, column,
+                            "Type mismatch! Object (%i) wasn't an integer.",
+                            json_typeof(root));
+                    return(-2);
                 }
-                break;
+                *va_arg(*ap, int*) = json_integer_value(root);
+                return(0);
 
-            case '[':
-            case '{':
-                /* Fetch object */
-                if(!cur)
-                {
-                    obj = root;
-                }
-                else if(json_is_object(cur))
-                {
-                    if(!key)
-                    {
-                        jsonp_error_set(error, line, -1,
-                              "Objects can't be keys");
-                        rv = -1;
-                        goto out;
-                    }
-                    obj = json_object_get(cur, key);
-                    unvisited--;
-                    key = NULL;
-                }
-                else if(json_is_array(cur))
+            case 'b':
+                if(!json_is_boolean(root))
                 {
-                    obj = json_array_get(cur, array_index);
-                    unvisited--;
-                    array_index++;
-                }
-                else
-                {
-                    assert(0);
+                    jsonp_error_set(error, line, column,
+                            "Type mismatch! Object (%i) wasn't a boolean.",
+                            json_typeof(root));
+                    return(-2);
                 }
+                *va_arg(*ap, int*) = json_is_true(root);
+                return(0);
 
-                /* Make sure we got what we expected */
-                if(*fmt=='{' && !json_is_object(obj))
+            case 'f':
+                if(!json_is_number(root))
                 {
-                    rv = -2;
-                    goto out;
+                    jsonp_error_set(error, line, column,
+                            "Type mismatch! Object (%i) wasn't a real.",
+                            json_typeof(root));
+                    return(-2);
                 }
+                *va_arg(*ap, double*) = json_number_value(root);
+                return(0);
 
-                if(*fmt=='[' && !json_is_array(obj))
-                {
-                    rv = -2;
-                    goto out;
-                }
+            case 'O':
+                json_incref(root);
+                /* Fall through */
+
+            case 'o':
+                *va_arg(*ap, json_t**) = root;
+                return(0);
 
-                unvisited += json_is_object(obj) ?
-                    json_object_size(obj) :
-                    json_array_size(obj);
+            case 'n':
+                /* Don't actually assign anything; we're just happy
+                 * the null turned up as promised in the format
+                 * string. */
+                return(0);
 
-                /* Descend */
-                stack[depth++] = cur;
-                cur = obj;
+            default:
+                jsonp_error_set(error, line, column,
+                        "Unknown format character '%c'", *tok);
+                return(-1);
+        }
+    }
 
-                key = NULL;
+    /* Move past container opening token */
+    tok++;
 
+    while(tok-fmt < size) {
+        switch(*tok) {
+            case '\n':
+                line++;
+                column=0;
                 break;
 
+            case ' ': /* Whitespace */
+                break;
 
-            case ']':
-            case '}':
+            case ',': /* Element spacer */
+                if(key)
+                {
+                    jsonp_error_set(error, line, column,
+                              "Expected KEY, got COMMA!");
+                    return(-2);
+                }
+                break;
 
-                if(json_is_array(cur) && *fmt!=']')
+            case ':': /* Key/value separator */
+                if(!key)
                 {
-                    jsonp_error_set(error, line, -1, "Missing ']'");
-                    rv = -1;
-                    goto out;
+                    jsonp_error_set(error, line, column,
+                              "Got key/value separator without "
+                              "a key preceding it!");
+                    return(-2);
                 }
 
-                if(json_is_object(cur) && *fmt!='}')
+                if(!json_is_object(root))
                 {
-                    jsonp_error_set(error, line, -1, "Missing '}'");
-                    rv = -1;
-                    goto out;
+                    jsonp_error_set(error, line, column,
+                              "Got a key/value separator "
+                              "(':') outside an object!");
+                    return(-2);
                 }
 
-                if(key)
+                break;
+
+            case ']': /* Close array or object */
+            case '}':
+
+                if(tok-fmt + (ssize_t)strspn(tok+1, " ") != size-1)
                 {
-                    jsonp_error_set(error, line, -1, "Unexpected '%c'", *fmt);
-                    rv = -1;
-                    goto out;
+                    jsonp_error_set(error, line, column,
+                              "Unexpected close-bracket '%c'", *tok);
+                    return(-2);
                 }
 
-                if(depth <= 0)
+                if((*tok == ']' && !json_is_array(root)) ||
+                   (*tok == '}' && !json_is_object(root)))
                 {
-                    jsonp_error_set(error, line, -1, "Unexpected '%c'", *fmt);
-                    rv = -1;
-                    goto out;
+                    jsonp_error_set(error, line, column,
+                              "Stray close-array '%c' character", *tok);
+                    return(-2);
                 }
+                return(unvisited);
+
+            case '[':
+            case '{':
 
-                cur = stack[--depth];
+                /* Find corresponding close bracket */
+                etok = tok+1;
+                etok_depth = 1;
+                while(etok_depth) {
 
-                break;
+                    if(!*etok || etok-fmt >= size) {
+                        jsonp_error_set(error, line, column,
+                                "Couldn't find matching close bracket for '%c'",
+                                *tok);
+                        return(-2);
+                    }
 
-            case 's':
-                if(!key && json_is_object(cur))
-                {
-                    /* constant string for key */
-                    key = va_arg(ap, char*);
-                    break;
+                    if(*tok==*etok)
+                        etok_depth++;
+                    else if(*tok=='[' && *etok==']') {
+                        etok_depth--;
+                        break;
+                    } else if(*tok=='{' && *etok=='}') {
+                        etok_depth--;
+                        break;
+                    }
+
+                    etok++;
                 }
-                /* fall through */
 
-            case 'i': /* integer */
-            case 'f': /* double-precision float */
-            case 'O': /* a json_t object; increments refcount */
-            case 'o': /* a json_t object; borrowed reference */
-            case 'b': /* boolean */
-            case 'n': /* null */
+                /* Recurse */
+                if(json_is_array(root)) {
+                    rv = json_vnunpack(json_object_get(root, key),
+                            error, etok-tok+1, tok, ap);
+                } else {
+                    rv = json_vnunpack(json_array_get(root, array_index++),
+                            error, etok-tok+1, tok, ap);
+                }
 
-                /* Fetch object */
-                if(!cur)
-                {
-                    obj = root;
+                if(rv < 0) {
+                    /* error should already be set */
+                    error->column += column-1;
+                    error->line += line-1;
+                    return(rv);
                 }
-                else if(json_is_object(cur))
+
+                unvisited += rv;
+                column += etok-tok;
+                tok = etok;
+                break;
+
+            case 's':
+                /* Handle strings specially, since they're used for both keys
+                 * and values */
+
+                if(json_is_object(root) && !key)
                 {
+                    /* It's a key */
+                    key = va_arg(*ap, char*);
+                    printf("Got key '%s'\n", key);
+
                     if(!key)
                     {
-                        jsonp_error_set(error, line, -1,
-                                  "Only strings may be used as keys!");
-                        rv = -1;
-                        goto out;
+                        jsonp_error_set(error, line, column,
+                                  "Refusing to handle a NULL key");
+                        return(-2);
                     }
-
-                    obj = json_object_get(cur, key);
-                    unvisited--;
-                    key = NULL;
-                }
-                else if(json_is_array(cur))
-                {
-                    obj = json_array_get(cur, array_index);
-                    unvisited--;
-                    array_index++;
+                    break;
                 }
+
+                /* Fall through */
+
+            default:
+
+                /* Fetch the element from the JSON container */
+                if(json_is_object(root))
+                    obj = json_object_get(root, key);
                 else
-                {
-                    jsonp_error_set(error, line, -1,
-                              "Unsure how to retrieve JSON object '%c'",
-                              *fmt);
-                    rv = -1;
-                    goto out;
+                    obj = json_array_get(root, array_index++);
+
+                if(!obj) {
+                    jsonp_error_set(error, line, column,
+                            "Array/object entry didn't exist!");
+                    return(-1);
                 }
 
-                switch(*fmt)
-                {
-                    case 's':
-                        if(!json_is_string(obj))
-                        {
-                            jsonp_error_set(error, line, -1,
-                                    "Type mismatch! Object wasn't a string.");
-                            rv = -2;
-                            goto out;
-                        }
-                        *va_arg(ap, const char**) = json_string_value(obj);
-                        break;
+                rv = json_vnunpack(obj, error, 1, tok, ap);
+                if(rv != 0)
+                    return(rv);
 
-                    case 'i':
-                        if(!json_is_integer(obj))
-                        {
-                            jsonp_error_set(error, line, -1,
-                                    "Type mismatch! Object wasn't an integer.");
-                            rv = -2;
-                            goto out;
-                        }
-                        *va_arg(ap, int*) = json_integer_value(obj);
-                        break;
+                break;
+        }
+        tok++;
+        column++;
+    }
 
-                    case 'b':
-                        if(!json_is_boolean(obj))
-                        {
-                            jsonp_error_set(error, line, -1,
-                                    "Type mismatch! Object wasn't a boolean.");
-                            rv = -2;
-                            goto out;
-                        }
-                        *va_arg(ap, int*) = json_is_true(obj);
-                        break;
+    /* Whoops -- we didn't match the close bracket! */
+    jsonp_error_set(error, line, column, "Missing close array or object!");
+    return(-2);
+}
 
-                    case 'f':
-                        if(!json_is_number(obj))
-                        {
-                            jsonp_error_set(error, line, -1,
-                                    "Type mismatch! Object wasn't a real.");
-                            rv = -2;
-                            goto out;
-                        }
-                        *va_arg(ap, double*) = json_number_value(obj);
-                        break;
+json_t *json_pack(json_error_t *error, const char *fmt, ...)
+{
+    va_list ap;
+    json_t *obj;
 
-                    case 'O':
-                        json_incref(obj);
-                        /* Fall through */
+    jsonp_error_init(error, "");
 
-                    case 'o':
-                        *va_arg(ap, json_t**) = obj;
-                        break;
+    if(!fmt || !*fmt) {
+        jsonp_error_set(error, 1, 1, "Null or empty format string!");
+        return(NULL);
+    }
 
-                    case 'n':
-                        /* Don't actually assign anything; we're just happy
-                         * the null turned up as promised in the format
-                         * string. */
-                        break;
+    va_start(ap, fmt);
+    obj = json_vnpack(error, strlen(fmt), fmt, &ap);
+    va_end(ap);
 
-                    default:
-                        jsonp_error_set(error, line, -1,
-                                "Unknown format character '%c'", *fmt);
-                        rv = -1;
-                        goto out;
-                }
-        }
-        fmt++;
-    }
+    return(obj);
+}
 
-    /* Return 0 if everything was matched; otherwise the number of JSON
-     * objects we didn't get to. */
-    rv = unvisited;
+int json_unpack(json_t *root, json_error_t *error, const char *fmt, ...)
+{
+    va_list ap;
+    int rv;
 
-out:
-    va_end(ap);
+    jsonp_error_init(error, "");
+
+    if(!fmt || !*fmt) {
+        jsonp_error_set(error, 1, 1, "Null or empty format string!");
+        return(-2);;
+    }
 
-    if(stack)
-        free(stack);
+    va_start(ap, fmt);
+    rv = json_vnunpack(root, error, strlen(fmt), fmt, &ap);
+    va_end(ap);
 
     return(rv);
 }
index f1ca388..8968dc7 100644 (file)
@@ -15,13 +15,14 @@ int main()
 {
     json_t *value;
     int i;
+    json_error_t error;
 
     /*
      * Simple, valid json_pack cases
      */
 
     /* true */
-    value = json_pack(NULL, "b", 1);
+    value = json_pack(&error, "b", 1);
     if(!json_is_true(value))
             fail("json_pack boolean failed");
     if(value->refcount != (ssize_t)-1)
@@ -29,7 +30,7 @@ int main()
     json_decref(value);
 
     /* false */
-    value = json_pack(NULL, "b", 0);
+    value = json_pack(&error, "b", 0);
     if(!json_is_false(value))
             fail("json_pack boolean failed");
     if(value->refcount != (ssize_t)-1)
@@ -37,7 +38,7 @@ int main()
     json_decref(value);
 
     /* null */
-    value = json_pack(NULL, "n");
+    value = json_pack(&error, "n");
     if(!json_is_null(value))
             fail("json_pack null failed");
     if(value->refcount != (ssize_t)-1)
@@ -45,7 +46,7 @@ int main()
     json_decref(value);
 
     /* integer */
-    value = json_pack(NULL, "i", 1);
+    value = json_pack(&error, "i", 1);
     if(!json_is_integer(value) || json_integer_value(value) != 1)
             fail("json_pack integer failed");
     if(value->refcount != (ssize_t)1)
@@ -54,7 +55,7 @@ int main()
 
 
     /* real */
-    value = json_pack(NULL, "f", 1.0);
+    value = json_pack(&error, "f", 1.0);
     if(!json_is_real(value) || json_real_value(value) != 1.0)
             fail("json_pack real failed");
     if(value->refcount != (ssize_t)1)
@@ -62,7 +63,7 @@ int main()
     json_decref(value);
 
     /* string */
-    value = json_pack(NULL, "s", "test");
+    value = json_pack(&error, "s", "test");
     if(!json_is_string(value) || strcmp("test", json_string_value(value)))
             fail("json_pack string failed");
     if(value->refcount != (ssize_t)1)
@@ -70,7 +71,7 @@ int main()
     json_decref(value);
 
     /* empty object */
-    value = json_pack(NULL, "{}", 1.0);
+    value = json_pack(&error, "{}", 1.0);
     if(!json_is_object(value) || json_object_size(value) != 0)
             fail("json_pack empty object failed");
     if(value->refcount != (ssize_t)1)
@@ -78,7 +79,7 @@ int main()
     json_decref(value);
 
     /* empty list */
-    value = json_pack(NULL, "[]", 1.0);
+    value = json_pack(&error, "[]", 1.0);
     if(!json_is_array(value) || json_array_size(value) != 0)
             fail("json_pack empty list failed");
     if(value->refcount != (ssize_t)1)
@@ -86,7 +87,7 @@ int main()
     json_decref(value);
 
     /* non-incref'd object */
-    value = json_pack(NULL, "o", json_integer(1));
+    value = json_pack(&error, "o", json_integer(1));
     if(!json_is_integer(value) || json_integer_value(value) != 1)
             fail("json_pack object failed");
     if(value->refcount != (ssize_t)1)
@@ -94,7 +95,7 @@ int main()
     json_decref(value);
 
     /* incref'd object */
-    value = json_pack(NULL, "O", json_integer(1));
+    value = json_pack(&error, "O", json_integer(1));
     if(!json_is_integer(value) || json_integer_value(value) != 1)
             fail("json_pack object failed");
     if(value->refcount != (ssize_t)2)
@@ -103,17 +104,17 @@ int main()
     json_decref(value);
 
     /* simple object */
-    value = json_pack(NULL, "{s:[]}", "foo");
+    value = json_pack(&error, "{s:[]}", "foo");
     if(!json_is_object(value) || json_object_size(value) != 1)
-            fail("json_pack object failed");
+            fail("json_pack array failed");
     if(!json_is_array(json_object_get(value, "foo")))
-            fail("json_pack object failed");
+            fail("json_pack array failed");
     if(json_object_get(value, "foo")->refcount != (ssize_t)1)
             fail("json_pack object refcount failed");
     json_decref(value);
 
     /* simple array */
-    value = json_pack(NULL, "[i,i,i]", 0, 1, 2);
+    value = json_pack(&error, "[i,i,i]", 0, 1, 2);
     if(!json_is_array(value) || json_array_size(value) != 3)
             fail("json_pack object failed");
     for(i=0; i<3; i++)
@@ -125,30 +126,82 @@ int main()
     }
     json_decref(value);
 
+    /* Whitespace; regular string */
+    value = json_pack(&error, " s ", "test");
+    if(!json_is_string(value) || strcmp("test", json_string_value(value)))
+            fail("json_pack string (with whitespace) failed");
+    json_decref(value);
+
+    /* Whitespace; empty array */
+    value = json_pack(&error, "[ ]");
+    if(!json_is_array(value) || json_array_size(value) != 0)
+            fail("json_pack empty array (with whitespace) failed");
+    json_decref(value);
+
+    /* Whitespace; array */
+    value = json_pack(&error, "[ i , i,  i ] ", 1, 2, 3);
+    if(!json_is_array(value) || json_array_size(value) != 3)
+            fail("json_pack array (with whitespace) failed");
+    json_decref(value);
+
     /*
      * Invalid cases
      */
-    
+
     /* mismatched open/close array/object */
-    if(json_pack(NULL, "[}"))
+    if(json_pack(&error, "[}"))
         fail("json_pack failed to catch mismatched '}'");
+    if(error.line != 1 || error.column != 2)
+        fail("json_pack didn't get the error coordinates right!");
 
-    if(json_pack(NULL, "{]"))
+    if(json_pack(&error, "{]"))
         fail("json_pack failed to catch mismatched ']'");
+    if(error.line != 1 || error.column != 2)
+        fail("json_pack didn't get the error coordinates right!");
 
     /* missing close array */
-    if(json_pack(NULL, "["))
+    if(json_pack(&error, "["))
         fail("json_pack failed to catch missing ']'");
+    if(error.line != 1 || error.column != 2)
+        fail("json_pack didn't get the error coordinates right!");
 
     /* missing close object */
-    if(json_pack(NULL, "{"))
+    if(json_pack(&error, "{"))
         fail("json_pack failed to catch missing '}'");
+    if(error.line != 1 || error.column != 2)
+        fail("json_pack didn't get the error coordinates right!");
 
     /* NULL string */
-    if(json_pack(NULL, "s", NULL))
-        fail("json_pack failed to catch null string");
+    if(json_pack(&error, "s", NULL))
+        fail("json_pack failed to catch null argument string");
+    if(error.line != 1 || error.column != 1)
+        fail("json_pack didn't get the error coordinates right!");
+
+    /* NULL format */
+    if(json_pack(&error, NULL))
+        fail("json_pack failed to catch NULL format string");
+    if(error.line != 1 || error.column != 1)
+        fail("json_pack didn't get the error coordinates right!");
+
+    /* More complicated checks for row/columns */
+    if(json_pack(&error, "{ {}: s }", "foo"))
+        fail("json_pack failed to catch object as key");
+    if(error.line != 1 || error.column != 3)
+        fail("json_pack didn't get the error coordinates right!");
+
+    if(json_pack(&error, "{ s: {},  s:[ii{} }", "foo", "bar", 12, 13))
+        fail("json_pack failed to catch missing ]");
+    if(error.line != 1 || error.column != 13)
+        fail("json_pack didn't get the error coordinates right!");
+
+    if(json_pack(&error, "[[[[[   [[[[[  [[[[ }]]]] ]]]] ]]]]]"))
+        fail("json_pack failed to catch missing ]");
+    if(error.line != 1 || error.column != 21)
+        fail("json_pack didn't get the error coordinates right!");
 
     return(0);
+
+    //fprintf(stderr, "%i/%i: %s %s\n", error.line, error.column, error.source, error.text);
 }
 
 /* vim: ts=4:expandtab:sw=4
index ea18ff8..6bc2e66 100644 (file)
@@ -20,90 +20,139 @@ int main()
     double f;
     char *s;
 
+    json_error_t error;
+
     /*
      * Simple, valid json_pack cases
      */
 
     /* true */
-    rv = json_unpack(json_true(), NULL, "b", &i1);
+    rv = json_unpack(json_true(), &error, "b", &i1);
     if(rv || !i1)
         fail("json_unpack boolean failed");
 
     /* false */
-    rv = json_unpack(json_false(), NULL, "b", &i1);
+    rv = json_unpack(json_false(), &error, "b", &i1);
     if(rv || i1)
         fail("json_unpack boolean failed");
 
     /* null */
-    rv = json_unpack(json_null(), NULL, "n");
+    rv = json_unpack(json_null(), &error, "n");
     if(rv)
         fail("json_unpack null failed");
 
     /* integer */
     j = json_integer(1);
-    rv = json_unpack(j, NULL, "i", &i1);
+    rv = json_unpack(j, &error, "i", &i1);
     if(rv || i1 != 1)
         fail("json_unpack integer failed");
     json_decref(j);
 
     /* real */
     j = json_real(1.0);
-    rv = json_unpack(j, NULL, "f", &f);
+    rv = json_unpack(j, &error, "f", &f);
     if(rv || f != 1.0)
         fail("json_unpack real failed");
     json_decref(j);
 
     /* string */
     j = json_string("foo");
-    rv = json_unpack(j, NULL, "s", &s);
+    rv = json_unpack(j, &error, "s", &s);
     if(rv || strcmp(s, "foo"))
         fail("json_unpack string failed");
     json_decref(j);
 
     /* empty object */
     j = json_object();
-    rv = json_unpack(j, NULL, "{}");
+    rv = json_unpack(j, &error, "{}");
     if(rv)
         fail("json_unpack empty object failed");
     json_decref(j);
 
     /* empty list */
     j = json_array();
-    rv = json_unpack(j, NULL, "[]");
+    rv = json_unpack(j, &error, "[]");
     if(rv)
         fail("json_unpack empty list failed");
     json_decref(j);
 
     /* non-incref'd object */
     j = json_object();
-    rv = json_unpack(j, NULL, "o", &j2);
+    rv = json_unpack(j, &error, "o", &j2);
     if(j2 != j || j->refcount != (ssize_t)1)
         fail("json_unpack object failed");
     json_decref(j);
 
     /* incref'd object */
     j = json_object();
-    rv = json_unpack(j, NULL, "O", &j2);
+    rv = json_unpack(j, &error, "O", &j2);
     if(j2 != j || j->refcount != (ssize_t)2)
         fail("json_unpack object failed");
     json_decref(j);
     json_decref(j);
 
     /* simple object */
-    j = json_pack(NULL, "{s:i}", "foo", 1);
-    rv = json_unpack(j, NULL, "{s:i}", "foo", &i1);
+    j = json_pack(&error, "{s:i}", "foo", 1);
+    rv = json_unpack(j, &error, "{s:i}", "foo", &i1);
     if(rv || i1!=1)
         fail("json_unpack simple object failed");
     json_decref(j);
 
     /* simple array */
-    j = json_pack(NULL, "[iii]", 1, 2, 3);
-    rv = json_unpack(j, NULL, "[i,i,i]", &i1, &i2, &i3);
+    j = json_pack(&error, "[iii]", 1, 2, 3);
+    rv = json_unpack(j, &error, "[i,i,i]", &i1, &i2, &i3);
     if(rv || i1 != 1 || i2 != 2 || i3 != 3)
         fail("json_unpack simple array failed");
     json_decref(j);
 
+    /*
+     * Invalid cases
+     */
+
+    /* mismatched open/close array/object */
+    j = json_pack(&error, "[]");
+    rv = json_unpack(j, &error, "[}");
+    if(!rv)
+        fail("json_unpack failed to catch mismatched ']'");
+    json_decref(j);
+
+    j = json_pack(&error, "{}");
+    rv = json_unpack(j, &error, "{]");
+    if(!rv)
+        fail("json_unpack failed to catch mismatched '}'");
+    json_decref(j);
+
+    /* missing close array */
+    j = json_pack(&error, "[]");
+    rv = json_unpack(j, &error, "[");
+    if(rv >= 0)
+        fail("json_unpack failed to catch missing ']'");
+    json_decref(j);
+
+    /* missing close object */
+    j = json_pack(&error, "{}");
+    rv = json_unpack(j, &error, "{");
+    if(rv >= 0)
+        fail("json_unpack failed to catch missing '}'");
+    json_decref(j);
+
+    /* NULL format string */
+    j = json_pack(&error, "[]");
+    rv =json_unpack(j, &error, NULL);
+    if(rv >= 0)
+        fail("json_unpack failed to catch null format string");
+    json_decref(j);
+
+    /* NULL string pointer */
+    j = json_string("foobie");
+    rv =json_unpack(j, &error, "s", NULL);
+    if(rv >= 0)
+        fail("json_unpack failed to catch null string pointer");
+    json_decref(j);
+
     return 0;
+
+    //fprintf(stderr, "%i/%i: %s %s\n", error.line, error.column, error.source, error.text);
 }
 
 /* vim: ts=4:expandtab:sw=4