Skip to content

Instantly share code, notes, and snippets.

@jabb
Created November 19, 2012 05:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jabb/4109122 to your computer and use it in GitHub Desktop.
Save jabb/4109122 to your computer and use it in GitHub Desktop.
JSON in C source file.
/* Copyright (c) 2012, Michael Patraw
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * The name of Michael Patraw may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY Michael Patraw ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Michael Patraw BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "jc.h"
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tree.h"
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
/******************************************************************************\
\******************************************************************************/
struct jc_obj_node
{
RB_ENTRY(jc_obj_node) entry;
char *key;
struct jc_atom *value;
};
static int jc_obj_node_cmp(struct jc_obj_node *a, struct jc_obj_node *b)
{
return strcmp(a->key, b->key);
}
RB_HEAD(jc_obj, jc_obj_node);
RB_GENERATE_STATIC(jc_obj, jc_obj_node, entry, jc_obj_node_cmp)
/******************************************************************************\
\******************************************************************************/
struct jc_arr
{
struct jc_atom **arr;
int allocd;
int length;
};
static int arr_resize(struct jc_arr *arr, int newsize)
{
struct jc_atom **tmp;
if (!arr->arr)
{
arr->arr = malloc(sizeof *arr * newsize);
if (!arr->arr)
return -1;
arr->allocd = newsize;
}
else
{
tmp = realloc(arr->arr, sizeof *arr * newsize);
if (!tmp)
return -1;
arr->arr = tmp;
arr->allocd = newsize;
}
return 0;
}
static int arr_insert(struct jc_arr *arr, int pos, struct jc_atom *atom)
{
int rv, i;
if (arr->length >= arr->allocd)
{
if (arr->allocd == 0)
arr->allocd = 1;
if ((rv = arr_resize(arr, arr->allocd * 2)))
return rv;
}
if (pos >= arr->allocd)
{
if (arr->allocd == 0)
arr->allocd = 1;
if ((rv = arr_resize(arr, MAX(arr->allocd * 2, pos + 1))))
return rv;
}
if (pos >= 0 && pos < arr->length)
{
for (i = arr->length; i > pos; --i)
arr->arr[i] = arr->arr[i - 1];
arr->arr[pos] = atom;
arr->length++;
}
else
{
for (i = arr->length; i < pos; ++i)
{
arr->arr[i] = jc_null();
if (!arr->arr[i])
return -1;
arr->length++;
}
arr->arr[pos] = atom;
arr->length++;
}
return 0;
}
static int arr_remove(struct jc_arr *arr, int pos)
{
int rv, i;
if (arr->length < (arr->allocd >> 1))
{
if ((rv = arr_resize(arr, arr->allocd >> 1)))
return rv;
}
for (i = pos; i < arr->length; ++i)
arr->arr[i] = arr->arr[i + 1];
arr->arr[i] = NULL;
arr->length--;
return 0;
}
static struct jc_atom *arr_get(struct jc_arr *arr, int pos)
{
if (pos >= 0 && pos < arr->length)
return arr->arr[pos];
return NULL;
}
/******************************************************************************\
\******************************************************************************/
static char *string_duplicate(const char *str)
{
char *dup = malloc(strlen(str) + 1);
if (!dup)
return NULL;
strcpy(dup, str);
return dup;
}
static char *string_append(char *a, const char *b)
{
char *s = realloc(a, strlen(a) + strlen(b) + 1);
if (!s)
return NULL;
strcat(s, b);
return s;
}
static char *string_expand(char *dest, const char *src)
{
char c;
while ((c = *(src++)))
{
switch (c)
{
case '\"':
*(dest++) = '\\';
*(dest++) = '\"';
break;
case '\\':
*(dest++) = '\\';
*(dest++) = '\\';
break;
case '\b':
*(dest++) = '\\';
*(dest++) = 'b';
break;
case '\f':
*(dest++) = '\\';
*(dest++) = 'f';
break;
case '\n':
*(dest++) = '\\';
*(dest++) = 'n';
break;
case '\r':
*(dest++) = '\\';
*(dest++) = 'r';
break;
case '\t':
*(dest++) = '\\';
*(dest++) = 't';
break;
default:
*(dest++) = c;
break;
}
}
*dest = '\0';
return dest;
}
static char *string_expand_alloc(const char *src)
{
char *dest = malloc(2 * strlen(src) + 1);
string_expand(dest, src);
return dest;
}
/******************************************************************************\
\******************************************************************************/
static struct jc_atom *jc_atom(int type, ...)
{
va_list args;
struct jc_atom *atom;
va_start(args, type);
atom = malloc(sizeof *atom);
if (!atom)
goto failure;
atom->type = type;
atom->ref = 0;
atom->iter = NULL;
switch (type)
{
case JC_BOOLEAN:
atom->guts.boolean = va_arg(args, int);
break;
case JC_NUMBER:
atom->guts.number = va_arg(args, double);
break;
case JC_STRING:
atom->guts.string = string_duplicate(va_arg(args, const char *));
if (!atom->guts.string)
goto failure;
break;
case JC_OBJECT:
atom->guts.object = malloc(sizeof(struct jc_obj));
if (!atom->guts.object)
goto failure;
memset(atom->guts.object, 0, sizeof(struct jc_obj));
break;
case JC_ARRAY:
atom->guts.array = malloc(sizeof(struct jc_arr));
memset(atom->guts.array, 0, sizeof(struct jc_arr));
break;
default: break;
}
va_end(args);
return atom;
failure:
va_end(args);
if (atom)
free(atom);
return NULL;
}
struct jc_atom *jc_null(void)
{
return jc_atom(JC_NULL);
}
struct jc_atom *jc_boolean(int tf)
{
return jc_atom(JC_BOOLEAN, tf);
}
struct jc_atom *jc_number(double num)
{
return jc_atom(JC_NUMBER, num);
}
struct jc_atom *jc_string(const char *str)
{
return jc_atom(JC_STRING, str);
}
struct jc_atom *jc_object(void)
{
return jc_atom(JC_OBJECT);
}
struct jc_atom *jc_array(void)
{
return jc_atom(JC_ARRAY);
}
struct jc_atom *jc_unref(struct jc_atom *atom)
{
int i;
struct jc_obj_node *node;
struct jc_obj_node *next;
if (--atom->ref > 0)
return atom;
switch (atom->type)
{
case JC_STRING:
free(atom->guts.string);
break;
case JC_OBJECT:
for (node = RB_MIN(jc_obj, atom->guts.object); node != NULL; node = next)
{
next = RB_NEXT(jc_obj, atom->guts.object, node);
RB_REMOVE(jc_obj, atom->guts.object, node);
jc_unref(node->value);
free(node->key);
free(node);
}
free(atom->guts.object);
break;
case JC_ARRAY:
for (i = 0; i < ((struct jc_arr *)atom->guts.array)->length; ++i)
jc_unref(((struct jc_arr *)atom->guts.array)->arr[i]);
if (((struct jc_arr *)atom->guts.array)->arr)
free(((struct jc_arr *)atom->guts.array)->arr);
free(atom->guts.array);
break;
default: break;
}
free(atom);
return NULL;
}
struct jc_atom *jc_ref(struct jc_atom *atom)
{
atom->ref++;
return atom;
}
int jc_type(struct jc_atom *atom)
{
if (!atom)
return JC_NULL;
return atom->type;
}
void *jc_value(struct jc_atom *atom)
{
switch (atom->type)
{
case JC_BOOLEAN:
return &atom->guts.boolean;
case JC_NUMBER:
return &atom->guts.number;
case JC_STRING:
return atom->guts.string;
default:
return NULL;
}
}
int jc_length(struct jc_atom *atom)
{
int len = 0;
struct jc_obj_node *node;
struct jc_obj_node *next;
switch (atom->type)
{
case JC_OBJECT:
for (node = RB_MIN(jc_obj, atom->guts.object); node != NULL; node = next)
{
next = RB_NEXT(jc_obj, atom->guts.object, node);
len++;
}
return len;
case JC_ARRAY:
return ((struct jc_arr *)atom->guts.array)->length;
default: return -1;
}
}
struct jc_atom *jc_set(struct jc_atom *obj, struct jc_atom *newatom, ...)
{
va_list args;
const char *key;
int index;
struct jc_obj_node *node = NULL, *toremove, find;
struct jc_atom *existing = NULL;
if (obj->type == JC_OBJECT)
{
va_start(args, newatom);
key = va_arg(args, const char *);
va_end(args);
existing = jc_get(obj, key);
if (existing || !newatom)
{
find.key = (char *)key;
toremove = RB_FIND(jc_obj, obj->guts.object, &find);
if (toremove)
{
RB_REMOVE(jc_obj, obj->guts.object, toremove);
jc_unref(toremove->value);
free(toremove->key);
free(toremove);
}
if (!newatom)
return NULL;
}
jc_ref(newatom);
node = malloc(sizeof *node);
if (!node)
goto failure;
node->key = string_duplicate(key);
if (!node->key)
goto failure;
node->value = newatom;
RB_INSERT(jc_obj, obj->guts.object, node);
return newatom;
}
else if (obj->type == JC_ARRAY)
{
va_start(args, newatom);
index = va_arg(args, int);
va_end(args);
existing = arr_get(obj->guts.array, index);
if (existing || !newatom)
{
jc_unref(arr_get(obj->guts.array, index));
arr_remove(obj->guts.array, index);
if (!newatom)
return NULL;
}
jc_ref(newatom);
if (arr_insert(obj->guts.array, index, newatom))
goto failure;
return newatom;
}
else
{
goto failure;
}
failure:
if (newatom)
jc_unref(newatom);
if (node)
free(node);
return NULL;
}
struct jc_atom *jc_get(struct jc_atom *obj, ...)
{
va_list args;
int index;
struct jc_obj_node find;
struct jc_obj_node *node;
if (obj->type == JC_OBJECT)
{
va_start(args, obj);
find.key = (char *)va_arg(args, const char *);
va_end(args);
node = RB_FIND(jc_obj, obj->guts.object, &find);
if (node)
return node->value;
else
return NULL;
}
else if (obj->type == JC_ARRAY)
{
va_start(args, obj);
index = va_arg(args, int);
va_end(args);
if (index == -1)
index = jc_length(obj) - 1;
return arr_get(obj->guts.array, index);
}
else
{
return NULL;
}
}
struct jc_atom *jc_vset(struct jc_atom *obj, struct jc_atom *newatom, const char *key)
{
int index;
struct jc_atom *find;
char *sub = NULL;
const char *end;
while (obj && *key)
{
if (obj->type == JC_OBJECT)
{
end = strstr(key, ".");
if (!end)
break;
sub = malloc(end - key + 1);
if (!sub)
return NULL;
strncpy(sub, key, end - key);
sub[end - key] = '\0';
find = jc_get(obj, sub);
if (find)
obj = find;
else
return NULL;
}
else if (obj->type == JC_ARRAY)
{
end = strstr(key, ".");
if (!end)
break;
sub = malloc(end - key + 1);
if (!sub)
return NULL;
strncpy(sub, key, end - key);
sub[end - key] = '\0';
sscanf(sub, "%d", &index);
if (index == -1)
index = jc_length(obj) - 1;
obj = arr_get(obj->guts.array, index);
}
else
{
return NULL;
}
if (end && *end)
key = end + 1;
else if (end)
key = end;
if (sub)
free(sub);
}
return jc_set(obj, newatom, key);
}
struct jc_atom *jc_vget(struct jc_atom *obj, const char *key)
{
int index;
struct jc_atom *find;
char *sub = NULL;
const char *end;
while (obj && *key)
{
if (obj->type == JC_OBJECT)
{
end = strstr(key, ".");
if (!end)
end = key + strlen(key);
sub = malloc(end - key + 1);
if (!sub)
return NULL;
strncpy(sub, key, end - key);
sub[end - key] = '\0';
find = jc_get(obj, sub);
if (find)
obj = find;
else
return NULL;
}
else if (obj->type == JC_ARRAY)
{
end = strstr(key, ".");
if (!end)
end = key + strlen(key);
sub = malloc(end - key + 1);
if (!sub)
return NULL;
strncpy(sub, key, end - key);
sub[end - key] = '\0';
sscanf(sub, "%d", &index);
if (index == -1)
index = jc_length(obj) - 1;
obj = arr_get(obj->guts.array, index);
}
else
{
return NULL;
}
if (end && *end)
key = end + 1;
else if (end)
key = end;
if (sub)
free(sub);
}
return obj;
}
int jc_iter(struct jc_atom *obj, void *key, struct jc_atom **val)
{
if (obj->type == JC_OBJECT)
{
if (!obj->iter)
{
obj->iter = RB_MIN(jc_obj, obj->guts.object);
}
else
{
obj->iter = RB_NEXT(jc_obj, obj->guts.object, obj->iter);
}
if (obj->iter)
{
if (key)
*((const char **)key) = ((struct jc_obj_node *)obj->iter)->key;
if (val)
*val = ((struct jc_obj_node *)obj->iter)->value;
return 1;
}
else
{
return 0;
}
}
else if (obj->type == JC_ARRAY)
{
if (!obj->iter)
{
obj->iter = (void *)1;
}
else
{
obj->iter = (void *)(((long)obj->iter) + 1);
}
if (obj->iter)
{
if ((long)obj->iter > ((struct jc_arr *)obj->guts.array)->length)
{
obj->iter = NULL;
return 0;
}
else
{
if (key)
*((int *)key) = ((long)obj->iter) - 1;
if (val)
*val = ((struct jc_arr *)obj->guts.array)->arr[((long)obj->iter) - 1];
return 1;
}
}
else
{
return 0;
}
}
else
{
return 0;
}
}
static struct jc_atom *_jc_from_string(const char **str);
static int _skip(const char **str, const char *tokens)
{
int match;
int i;
int len = strlen(tokens);
while (**str)
{
match = 0;
for (i = 0; i < len; ++i)
{
if (**str == tokens[i])
match = 1;
}
if (!match)
return 1;
(*str)++;
}
return 0;
}
static int _skip_once(const char **str, const char *tokens, char once)
{
int match;
int i;
int len = strlen(tokens);
while (**str)
{
if (**str == once)
{
(*str)++;
if (!**str)
return 0;
else
return 1;
}
match = 0;
for (i = 0; i < len; ++i)
{
if (**str == tokens[i])
match = 1;
}
if (!match)
return 1;
(*str)++;
}
return 0;
}
static struct jc_atom *_jc_parse_null(const char **str)
{
if (strstr(*str, "null") == *str)
{
*str += 4;
return jc_null();
}
else
{
return NULL;
}
}
static struct jc_atom *_jc_parse_boolean(const char **str)
{
if (strstr(*str, "true") == *str)
{
*str += 4;
return jc_boolean(1);
}
else if (strstr(*str, "false") == *str)
{
*str += 5;
return jc_boolean(0);
}
else
{
return NULL;
}
}
static struct jc_atom *_jc_parse_number(const char **str)
{
double number;
const char *end = *str;
while (isdigit(*end) || *end == '.' || *end == 'e' || *end == 'E' || *end == '+' || *end == '-')
{
end++;
}
sscanf(*str, "%lf", &number);
*str = end;
return jc_number(number);
}
static struct jc_atom *_jc_parse_string(const char **str)
{
struct jc_atom *atom = NULL;
int i = 0;
int mallocd = 1024;
char *buffer = malloc(mallocd), *tmp;
if (!buffer)
goto failure;
memset(buffer, 0, mallocd);
_skip_once(str, " \t\n\r", '"');
while (**str)
{
if (**str == '"')
break;
if (**str == '\\' && *((*str) + 1) && *((*str) + 1) != 'u')
{
(*str)++;
if (!**str)
break;
if (**str == '"')
buffer[i] = '"';
else if (**str == '\\')
buffer[i] = '\\';
else if (**str == 'b')
buffer[i] = '\b';
else if (**str == 'f')
buffer[i] = '\f';
else if (**str == 'n')
buffer[i] = '\n';
else if (**str == 'r')
buffer[i] = '\r';
else if (**str == 't')
buffer[i] = '\t';
else
buffer[i] = **str;
}
else
{
buffer[i] = **str;
}
buffer[i + 1] = 0;
(*str)++;
i++;
if (i + 1 >= mallocd)
{
mallocd *= 2;
tmp = realloc(buffer, mallocd);
if (!tmp)
goto failure;
buffer = tmp;
}
}
_skip_once(str, " \t\n\r", '"');
atom = jc_string(buffer);
if (!atom)
goto failure;
free(buffer);
return atom;
failure:
if (buffer)
free(buffer);
return NULL;
}
static struct jc_atom *_jc_parse_object(const char **str)
{
struct jc_atom *key = NULL;
struct jc_atom *atom = jc_object(), *elem;
if (!atom)
goto failure;
_skip_once(str, " \t\n\r", '{');
while (**str)
{
if (!_skip(str, " \t\n\r,"))
break;
if (**str == '}')
break;
key = _jc_parse_string(str);
if (jc_type(key) != JC_STRING)
goto failure;
if (!_skip(str, " \t\n\r:"))
break;
elem = _jc_from_string(str);
if (!elem)
goto failure;
if (!jc_set(atom, elem, jc_value(key)))
goto failure;
jc_unref(key);
}
_skip_once(str, " \t\n\r", '}');
return atom;
failure:
if (key)
jc_unref(key);
if (atom)
jc_unref(atom);
return NULL;
}
static struct jc_atom *_jc_parse_array(const char **str)
{
struct jc_atom *atom = jc_array(), *elem;
if (!atom)
goto failure;
_skip_once(str, " \t\n\r", '[');
while (**str)
{
if (!_skip(str, " \t\n\r,"))
break;
if (**str == ']')
break;
elem = _jc_from_string(str);
if (!elem)
goto failure;
if (!jc_set(atom, elem, jc_length(atom)))
goto failure;
}
_skip_once(str, " \t\n\r", ']');
return atom;
failure:
if (atom)
jc_unref(atom);
return NULL;
}
static struct jc_atom *_jc_from_string(const char **str)
{
int c;
while ((c = **str))
{
switch (c)
{
case ' ': case '\t': case '\n': case '\r':
break;
case 'n':
return _jc_parse_null(str);
case 't': case 'f':
return _jc_parse_boolean(str);
case '-':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
return _jc_parse_number(str);
case '"':
return _jc_parse_string(str);
case '{':
return _jc_parse_object(str);
case '[':
return _jc_parse_array(str);
default:
return NULL;
}
if (!**str)
break;
(*str)++;
}
return NULL;
}
struct jc_atom *jc_from_string(const char *str)
{
return _jc_from_string(&str);
}
static char *_jc_to_string(struct jc_atom *atom, char *str)
{
int i;
struct jc_obj_node *node;
struct jc_obj_node *next;
char buffer[1024];
char *newstr = str, *tmpstr = NULL;
switch (atom->type)
{
case JC_NULL:
newstr = string_append(str, "null");
if (!newstr)
goto failure;
break;
case JC_BOOLEAN:
newstr = string_append(str, atom->guts.boolean ? "true" : "false");
if (!newstr)
goto failure;
break;
case JC_NUMBER:
sprintf(buffer, "%g", atom->guts.number);
newstr = string_append(str, buffer);
if (!newstr)
goto failure;
break;
case JC_STRING:
newstr = string_append(str, "\"");
if (!newstr)
goto failure;
str = newstr;
tmpstr = string_expand_alloc(atom->guts.string);
if (!tmpstr)
goto failure;
newstr = string_append(str, tmpstr);
if (!newstr)
goto failure;
free(tmpstr);
tmpstr = NULL;
str = newstr;
newstr = string_append(str, "\"");
if (!newstr)
goto failure;
str = newstr;
break;
case JC_OBJECT:
newstr = string_append(str, "{");
if (!newstr)
goto failure;
str = newstr;
for (node = RB_MIN(jc_obj, atom->guts.object); node != NULL; node = next)
{
next = RB_NEXT(jc_obj, atom->guts.object, node);
newstr = string_append(str, "\"");
if (!newstr)
goto failure;
str = newstr;
tmpstr = string_expand_alloc(node->key);
if (!tmpstr)
goto failure;
newstr = string_append(str, tmpstr);
if (!newstr)
goto failure;
free(tmpstr);
tmpstr = NULL;
str = newstr;
newstr = string_append(str, "\":");
if (!newstr)
goto failure;
str = newstr;
str = _jc_to_string(node->value, str);
if (!str)
goto failure;
if (next != NULL)
{
newstr = string_append(str, ",");
if (!newstr)
goto failure;
str = newstr;
}
}
newstr = string_append(str, "}");
if (!newstr)
goto failure;
break;
case JC_ARRAY:
newstr = string_append(str, "[");
if (!newstr)
goto failure;
str = newstr;
for (i = 0; i < jc_length(atom); ++i)
{
str = _jc_to_string(jc_get(atom, i), str);
if (!str)
goto failure;
if (i + 1 < jc_length(atom))
{
newstr = string_append(str, ",");
if (!newstr)
goto failure;
str = newstr;
}
}
newstr = string_append(str, "]");
if (!newstr)
goto failure;
break;
default: break;
}
return newstr;
failure:
if (str)
free(str);
if (tmpstr)
free(tmpstr);
return NULL;
}
char *jc_to_string(struct jc_atom *atom)
{
return _jc_to_string(atom, string_duplicate(""));
}
static void _jc_print(FILE *fd, struct jc_atom *atom, int tablevel)
{
int i, j;
struct jc_obj_node *node;
struct jc_obj_node *next;
char *tmpstr;
switch (atom->type)
{
case JC_NULL:
fprintf(fd, "null");
break;
case JC_BOOLEAN:
fprintf(fd, atom->guts.boolean ? "true" : "false");
break;
case JC_NUMBER:
fprintf(fd, "%g", atom->guts.number);
break;
case JC_STRING:
tmpstr = string_expand_alloc(atom->guts.string);
if (!tmpstr)
return;
fprintf(fd, "\"%s\"", tmpstr);
free(tmpstr);
break;
case JC_OBJECT:
fprintf(fd, "{\n");
for (node = RB_MIN(jc_obj, atom->guts.object); node != NULL; node = next)
{
next = RB_NEXT(jc_obj, atom->guts.object, node);
tmpstr = string_expand_alloc(node->key);
if (!tmpstr)
return;
for (j = 0; j < tablevel + 1; ++j)
fprintf(fd, "\t");
fprintf(fd, "\"%s\": ", tmpstr);
free(tmpstr);
_jc_print(fd, node->value, tablevel + 1);
if (next != NULL)
fprintf(fd, ",\n");
else
fprintf(fd, "\n");
}
for (j = 0; j < tablevel; ++j)
fprintf(fd, "\t");
fprintf(fd, "}");
break;
case JC_ARRAY:
fprintf(fd, "[\n");
for (i = 0; i < jc_length(atom); ++i)
{
for (j = 0; j < tablevel + 1; ++j)
fprintf(fd, "\t");
_jc_print(fd, jc_get(atom, i), tablevel + 1);
if (i + 1 < jc_length(atom))
fprintf(fd, ",\n");
else
fprintf(fd, "\n");
}
for (j = 0; j < tablevel; ++j)
fprintf(fd, "\t");
fprintf(fd, "]");
break;
default: break;
}
}
void jc_print(void *fd, struct jc_atom *atom)
{
_jc_print(fd, atom, 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment