diff --git a/README.md b/README.md index 54a317a..7ed9f01 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,6 @@ struct xj_value { }; }; ``` - objects and arrays are represented as linked lists of `xj_value`s. Although the user can make as many `xj_value` nodes as he wants, some constructor functions are also provided @@ -73,7 +72,7 @@ xj_alloc *xj_alloc_new(int size, int ext); ``` the first lets you specify the memory that the allocator will use to operate, while the second tells the allocator to call `malloc` to get the memory he needs. -When an `xj_alloc` is instanciated, you can call all of the functions that require you to provide an 'xj_alloc*'. All of the objects that those functions return, will be stored in the allocator. You deallocate all of the nodes by freeing up the whole allocator using +When an `xj_alloc` is instanciated, you can call all of the functions that require you to provide an `xj_alloc*`. The objects that those functions return will be stored in the allocator. You deallocate all of the nodes by freeing up the whole allocator using ```c void xj_alloc_del(xj_alloc *alloc); ``` \ No newline at end of file diff --git a/xjson.c b/xjson.c index a43d385..fec373e 100644 --- a/xjson.c +++ b/xjson.c @@ -7,6 +7,16 @@ #include #include "xjson.h" +/* OVERVIEW + * This file implements the routines required to + * decode and encode JSON text. These features are + * exposed through the [xj_decode] and [xj_encode] + * functions. + * + * Here is also designed the object model required + * to represent the encoded form of the JSON text. + */ + typedef struct chunk_t chunk_t; struct chunk_t { chunk_t *prev; @@ -21,6 +31,9 @@ struct xj_alloc { int ext_size; }; +/* Symbol: xj_alloc_new + * Instanciate an allocator. + */ xj_alloc *xj_alloc_new(int size, int ext) { assert(size >= 0 && ext >= 0); @@ -387,7 +400,7 @@ typedef struct { ** [nbytes] are ever written. If one of those conitions isn't true, -1 is ** returned. */ -int xutf8_sequence_from_utf32_codepoint(char *utf8_data, int nbytes, uint32_t utf32_code) +static int xutf8_sequence_from_utf32_codepoint(char *utf8_data, int nbytes, uint32_t utf32_code) { if(utf32_code < 128) { @@ -465,7 +478,7 @@ int xutf8_sequence_from_utf32_codepoint(char *utf8_data, int nbytes, uint32_t ut ** NOTE: By calling this function with a NULL [utf32_code], you can check the ** validity of a UTF-8 sequence. */ -int xutf8_sequence_to_utf32_codepoint(const char *utf8_data, int nbytes, uint32_t *utf32_code) +static int xutf8_sequence_to_utf32_codepoint(const char *utf8_data, int nbytes, uint32_t *utf32_code) { assert(utf8_data != NULL); assert(nbytes >= 0); @@ -592,7 +605,7 @@ typedef struct { char maybe[256]; } string_parsing_context_t; -_Bool spc_append(string_parsing_context_t *spc, const char *str, int len) +static _Bool spc_append(string_parsing_context_t *spc, const char *str, int len) { if(spc->size + len > spc->capacity) { @@ -631,7 +644,7 @@ _Bool spc_append(string_parsing_context_t *spc, const char *str, int len) return 1; } -void spc_free(string_parsing_context_t *spc) +static void spc_free(string_parsing_context_t *spc) { if(spc->maybe != spc->buffer) free(spc->buffer); @@ -1277,6 +1290,39 @@ static xj_value *parse_value(context_t *ctx) return NULL; } +/* Symbol: + * xj_decode + * + * Description: + * Transform a JSON UTF-8 string to a tree of [xj_value] nodes. + * + * Arguments: + * str: The string to be parsed. It's doesn't need to be + * zero-terminated. If NULL, an empty string is assumed. + * + * len: The length of [str] (in bytes). If negative, [str] is + * assumed to be zero-terminated and [len] is computed + * using [strlen]. + * + * alloc: The allocator that will be used to store the parsing + * result. It's not optional (can't be NULL). + * + * error: The reference to a caller-allocated [xj_error]. If + * an error occurres (NULL is returned) then this is + * used to provide the caller with useful information + * regarting the failure. It's not required and can be + * NULL. + * + * Returns: + * The pointer to a tree of [xj_value] nodes, or NULL on failure. + * If NULL is returned and an [xj_error] is provided, than it's + * fields are set to provide the caller with extra information + * related to the failure. + * + * Notes: + * The returned objects are deallocated with the whole allocator + * when calling [xj_alloc_del]. + */ xj_value *xj_decode(const char *str, int len, xj_alloc *alloc, xj_error *error) { @@ -1308,22 +1354,89 @@ xj_value *xj_decode(const char *str, int len, } typedef struct bucket_t bucket_t; + +/* Symbol: + * bucket_t + * + * Description: + * A memory region that linked with other [bucket_t] + * can represent long strings of text. It's a sub-type + * of [bucket_t]. + * + * Notes: + * This is a big structure. + * + * The [body]'s was chosen to be such that the whole + * [bucket_t] is 4kb big, but it's not really necessary. + */ struct bucket_t { bucket_t *next; char body[4096-sizeof(void*)]; }; +/* Symbol: + * buffer_t + * + * Description: + * A buffer that can be used to build large strings + * without the degradation of performance that one + * would get by using a plain dinamically growing + * array. + * It's implemented as a linked list of chunks, so + * it grows by adding new chunks, without the need + * to move the old chunks. + * + * Fields: + * size: The absolute string size (in bytes) that is + * contained in the buffer. When the buffer is + * serialized, the resulting string will have + * this size. + * + * used: The amount of bytes held by the last chunk. + * + * tail: The pointer to the last chunk. + * + * head: The first chunk of the buffer. It's not a + * pointer because it's pre-allocated with + * the [buffer_t]. + * + * Notes: + * The fact that the first chunk comes preallocated with + * the buffer makes it a large structure. A [bucket_t] is + * around 4kb, so a buffer will be bigger than that. + * + * The [head] is the last field so that the other fields + * are contiguous in memory. If [head] were between other + * fields, then there would be a 4kb distance between them. + */ typedef struct { int size, used; bucket_t *tail, head; } buffer_t; -static xj_bool append_string(buffer_t *buff, const char *str, int len) +/* Symbol: + * buffer_append + * + * Description: + * Appends a string to a [buffer_t]. + * + * Returns: + * 1 if all went well or 0 if an error occurred. + */ +static xj_bool buffer_append(buffer_t *buff, const char *str, int len) { assert(str != NULL && len >= 0); + // If there's not enough memory in the tail chunk + // then create a new tail chunk! + if(buff->used + len > (int) sizeof(buff->tail->body)) { + // It's not possible to add a string that + // is bigger than a chunk. + if(len > (int) sizeof(buff->tail->body)) + retunr 0; + bucket_t *buck = malloc(sizeof(bucket_t)); if(buck == NULL) @@ -1334,15 +1447,27 @@ static xj_bool append_string(buffer_t *buff, const char *str, int len) buff->tail = buck; buff->used = 0; } + memcpy(buff->tail->body + buff->used, str, len); buff->used += len; buff->size += len; return 1; } +/* Symbol: + * encode_string + * + * Description: + * Serializes a string to a [buffer_t] in JSON form. + * + * Returns: + * 1 if all went well or 0 if an error occurred. + */ static _Bool encode_string(const char *str, int len, buffer_t *buff) { - if(!append_string(buff, "\"", 1)) + assert(str != NULL && len >= 0); + + if(!buffer_append(buff, "\"", 1)) return 0; int i = 0; @@ -1357,7 +1482,7 @@ static _Bool encode_string(const char *str, int len, buffer_t *buff) int end = i; - if(!append_string(buff, str + start, end - start)) + if(!buffer_append(buff, str + start, end - start)) return 0; if(i == len) @@ -1365,13 +1490,13 @@ static _Bool encode_string(const char *str, int len, buffer_t *buff) if(str[i] == '"') { - if(!append_string(buff, "\\\"", 2)) + if(!buffer_append(buff, "\\\"", 2)) return 0; i += 1; } else if(str[i] == '\\') { - if(!append_string(buff, "\\\\", 2)) + if(!buffer_append(buff, "\\\\", 2)) return 0; i += 1; } @@ -1393,7 +1518,7 @@ static _Bool encode_string(const char *str, int len, buffer_t *buff) assert(m != NULL); - if(!append_string(buff, m, 2)) + if(!buffer_append(buff, m, 2)) return 0; i += 1; @@ -1443,30 +1568,39 @@ static _Bool encode_string(const char *str, int len, buffer_t *buff) buffer[12] = '\0'; } - if(!append_string(buff, buffer, used)) + if(!buffer_append(buff, buffer, used)) return 0; i += scanned; } } - if(!append_string(buff, "\"", 1)) + if(!buffer_append(buff, "\"", 1)) return 0; return 1; } +/* Symbol: + * encode_value + * + * Description: + * Serializes an [xj_value] to a [buffer_t] + * + * Returns: + * 1 if all went well or 0 if an error occurred. + */ static _Bool encode_value(xj_value *val, buffer_t *buff) { switch(val == NULL ? XJ_NULL : val->type) { case XJ_NULL: - return append_string(buff, "null", 4); + return buffer_append(buff, "null", 4); case XJ_BOOL: return val->as_bool - ? append_string(buff, "true", 4) - : append_string(buff, "false", 5); + ? buffer_append(buff, "true", 4) + : buffer_append(buff, "false", 5); case XJ_INT: { @@ -1474,7 +1608,7 @@ static _Bool encode_value(xj_value *val, buffer_t *buff) int k = snprintf(temp, sizeof(temp), "%lld", val->as_int); assert(k >= 0 && k < (int) sizeof(temp)); - if(!append_string(buff, temp, k)) + if(!buffer_append(buff, temp, k)) return 0; return 1; } @@ -1485,14 +1619,14 @@ static _Bool encode_value(xj_value *val, buffer_t *buff) int k = snprintf(temp, sizeof(temp), "%g", val->as_float); assert(k >= 0 && k < (int) sizeof(temp)); - if(!append_string(buff, temp, k)) + if(!buffer_append(buff, temp, k)) return 0; return 1; } case XJ_ARRAY: { - if(!append_string(buff, "[", 1)) + if(!buffer_append(buff, "[", 1)) return 0; xj_value *child = val->as_object; @@ -1504,18 +1638,18 @@ static _Bool encode_value(xj_value *val, buffer_t *buff) child = child->next; if(child != NULL) - if(!append_string(buff, ", ", 2)) + if(!buffer_append(buff, ", ", 2)) return 0; } - if(!append_string(buff, "]", 1)) + if(!buffer_append(buff, "]", 1)) return 0; return 1; } case XJ_OBJECT: { - if(!append_string(buff, "{", 1)) + if(!buffer_append(buff, "{", 1)) return 0; xj_value *child = val->as_object; @@ -1524,7 +1658,7 @@ static _Bool encode_value(xj_value *val, buffer_t *buff) if(!encode_string(child->key, strlen(child->key), buff)) return 0; - if(!append_string(buff, ": ", 2)) + if(!buffer_append(buff, ": ", 2)) return 0; if(!encode_value(child, buff)) @@ -1533,11 +1667,11 @@ static _Bool encode_value(xj_value *val, buffer_t *buff) child = child->next; if(child != NULL) - if(!append_string(buff, ", ", 2)) + if(!buffer_append(buff, ", ", 2)) return 0; } - if(!append_string(buff, "}", 1)) + if(!buffer_append(buff, "}", 1)) return 0; return 1; } @@ -1548,6 +1682,27 @@ static _Bool encode_value(xj_value *val, buffer_t *buff) return 0; } +/* Symbol: + * xj_encode + * + * Description: + * Transforms an [xj_value] to a string. + * + * Arguments: + * value: The object to be converted to a string. + * + * len: An output argument that returns the length + * of the generated string. It's optional, so + * it can be NULL. + * + * Returns: + * The pointer to a zero-terminated string if all went + * well or NULL. + * + * Notes: + * The returned pointer, if not NULL, must be + * deallocated using [free]. + */ char *xj_encode(xj_value *value, int *len) { buffer_t buff;