commit 3af2ba02f79e6b30469ec80dfc79a3ac7512e22c Author: cozis Date: Sat Apr 2 18:51:04 2022 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30d74d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..8096da7 --- /dev/null +++ b/build.sh @@ -0,0 +1 @@ +gcc test.c xjson.c -o test -Wall -Wextra -g \ No newline at end of file diff --git a/test.c b/test.c new file mode 100644 index 0000000..7c9bc5f --- /dev/null +++ b/test.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include "xjson.h" + +int main() +{ + char pool[4096]; + xj_error error; + xj_alloc *alloc; + + alloc = xj_newAllocUsing(pool, sizeof(pool), 0, NULL); + assert(alloc != NULL); + + xj_value *val = xj_decode("{}", -1, alloc, &error); + + if(val == NULL) + { + fprintf(stderr, "Failed to parse (%s)\n", error.message); + return 1; + } + fprintf(stdout, "Parsed.\n"); + + int size; + char *serialized = xj_encode(val, &size); + fprintf(stdout, "(%d) %s\n", size, serialized); + free(serialized); + return 0; +} \ No newline at end of file diff --git a/xjson.c b/xjson.c new file mode 100644 index 0000000..9fa33db --- /dev/null +++ b/xjson.c @@ -0,0 +1,814 @@ +#include +#include +#include +#include +#include +#include +#include "xjson.h" + +typedef struct chunk_t chunk_t; +struct chunk_t { + chunk_t *prev; + _Alignas(void*) char body[]; +}; + +struct xj_alloc { + void (*free)(void*); + chunk_t *tail; + int tail_used; + int tail_size; + int ext_size; +}; + +xj_alloc *xj_newAlloc(int size, int ext) +{ + assert(size >= 0 && ext >= 0); + + void *temp = malloc(sizeof(xj_alloc) + size); + + if(temp == NULL) + return NULL; + + return xj_newAllocUsing(temp, sizeof(xj_alloc) + size, ext, free); +} + +xj_alloc *xj_newAllocUsing(void *mem, int size, int ext, void (*free)(void*)) +{ + assert(mem != NULL && size >= 0 && ext >= 0); + + if((unsigned int) size < sizeof(xj_alloc) + sizeof(chunk_t)) + return NULL; + + xj_alloc *alloc = mem; + alloc->free = free; + alloc->tail = (chunk_t*) (alloc + 1); + alloc->tail->prev = NULL; + alloc->tail_used = 0; + alloc->tail_size = size - sizeof(xj_alloc); + alloc->ext_size = ext; + return alloc; +} + +void xj_freeAlloc(xj_alloc *alloc) +{ + if(alloc->free != NULL) + alloc->free(alloc); +} + +unsigned long long next_aligned(unsigned long long n) +{ + return n & 7 ? n & ~7 : n; +} + +void *xj_bpalloc(xj_alloc *alloc, int size) +{ + assert(size >= 0); + + alloc->tail_used = next_aligned(alloc->tail_used); + + if(alloc->tail_used + size > alloc->tail_size) + { + // Create a new chunk and append it + // to the allocator. + + if(alloc->ext_size == 0) + return NULL; + + chunk_t *chunk = malloc(sizeof(chunk_t) + alloc->ext_size); + + if(chunk == NULL) + return NULL; + + chunk->prev = alloc->tail; + alloc->tail = chunk; + alloc->tail_used = 0; + alloc->tail_size = alloc->ext_size; + } + + return alloc->tail->body + (alloc->tail_used += size); +} + +xj_value *xj_newNull(xj_alloc *alloc) +{ + xj_value *x = xj_bpalloc(alloc, sizeof(xj_value)); + if(x != NULL) + { + x->type = XJ_NULL; + x->size = -1; + x->next = NULL; + x->key = NULL; + } + return x; +} + +xj_value *xj_newBool(xj_bool val, xj_alloc *alloc) +{ + xj_value *x = xj_newNull(alloc); + if(x != NULL) + { + x->type = XJ_BOOL; + x->as_bool = val; + } + return x; +} + +xj_value *xj_newInt(xj_i64 val, xj_alloc *alloc) +{ + xj_value *x = xj_newNull(alloc); + if(x != NULL) + { + x->type = XJ_INT; + x->as_int = val; + } + return x; +} + +xj_value *xj_newFloat(xj_f64 val, xj_alloc *alloc) +{ + xj_value *x = xj_newNull(alloc); + if(x != NULL) + { + x->type = XJ_FLOAT; + x->as_float = val; + } + return x; +} + +xj_value *xj_newString(const char *str, int len, xj_alloc *alloc) +{ + if(str == NULL) str = ""; + if(len < 0) len = strlen(str); + + char *copy = xj_strdup(str, len, alloc); + + if(copy == NULL) + return NULL; + + xj_value *x = xj_newNull(alloc); + if(x != NULL) + { + x->type = XJ_STRING; + x->size = len; + x->as_string = copy; + } + return x; +} + +xj_value *xj_newArray__nocheck(xj_value *head, int count, xj_alloc *alloc) +{ + if(count < 0) + { + count = 0; + xj_value *curs = head; + while(curs != NULL) + { + count += 1; + curs = curs->next; + } + } + + xj_value *x = xj_newNull(alloc); + if(x != NULL) + { + x->type = XJ_ARRAY; + x->size = count; + x->as_array = head; + } + return x; +} + +xj_value *xj_newArray(xj_value *head, xj_alloc *alloc) +{ + int count = 0; + xj_value *curs = head; + while(curs != NULL) + { + if(curs->key != NULL) + { + /* Array child has a + key associated to it? */ + return NULL; + } + count += 1; + curs = curs->next; + } + + return xj_newArray__nocheck(head, count, alloc); +} + +xj_value *xj_newObject__nocheck(xj_value *head, int count, xj_alloc *alloc) +{ + if(count < 0) + { + count = 0; + xj_value *curs = head; + while(curs != NULL) + { + count += 1; + curs = curs->next; + } + } + + xj_value *x = xj_newNull(alloc); + if(x != NULL) + { + x->type = XJ_OBJECT; + x->size = count; + x->as_object = head; + } + return x; +} + +xj_value *xj_newObject(xj_value *head, xj_alloc *alloc) +{ + int count = 0; + xj_value *curs = head; + while(curs != NULL) + { + if(curs->key == NULL) + { + /* Object child has no + key associated to it! */ + return NULL; + } + + xj_value *curs2 = head; + while(curs2 != curs) + { + if(!strcmp(curs->key, curs2->key)) + { + /* Duplicate key. */ + return NULL; + } + curs2 = curs2->next; + } + + count += 1; + curs = curs->next; + } + + return xj_newObject__nocheck(head, count, alloc); +} + +char *xj_strdup(const char *str, int len, xj_alloc *alloc) +{ + assert(str != NULL); + + if(len < 0) + len = strlen(str); + + char *copy = xj_bpalloc(alloc, len+1); + + if(copy != NULL) + { + memcpy(copy, str, len); + copy[len] = '\0'; + } + return copy; +} + +static void xj_report(xj_error *error, const char *fmt, ...) +{ + if(error != NULL) + { + int k; + va_list va; + va_start(va, fmt); + k = vsnprintf(error->message, sizeof(error->message), fmt, va); + va_end(va); + + assert(k >= 0); + + error->truncated = (k >= (int) sizeof(error->message)-1); + error->occurred = 1; + } +} + +typedef struct { + const char *str; + int i, len; + xj_alloc *alloc; + xj_error *error; +} context_t; + +static void *parseString(context_t *ctx, _Bool raw) +{ + assert(ctx->i < ctx->len && ctx->str[ctx->i] == '"'); + + ctx->i += 1; // Skip '"'. + + int start = ctx->i; + + while(ctx->i < ctx->len && ctx->str[ctx->i] != '"') + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside a string"); + return NULL; + } + + int end = ctx->i; + + ctx->i += 1; // Skip '"'. + + return raw ? (void*) xj_strdup(ctx->str + start, end - start, ctx->alloc) + : (void*) xj_newString(ctx->str + start, end - start, ctx->alloc); +} + +static xj_value *parseNumber(context_t *ctx) +{ + assert(ctx->i < ctx->len && isdigit(ctx->str[ctx->i])); + + xj_i64 parsed = 0; + + while(ctx->i < ctx->len && isdigit(ctx->str[ctx->i])) + { + parsed = parsed * 10 + ctx->str[ctx->i] - '0'; + ctx->i += 1; + } + + xj_bool followed_by_dot = ctx->i+1 < ctx->len && ctx->str[ctx->i] == '.' && isdigit(ctx->str[ctx->i+1]); + + if(followed_by_dot) + { + ctx->i += 1; // Skip '.'. + + xj_f64 parsed2 = parsed, f = 1.0; + + while(ctx->i < ctx->len && isdigit(ctx->str[ctx->i])) + { + f /= 10; + parsed2 += f * (ctx->str[ctx->i] - '0'); + ctx->i += 1; + } + + return xj_newFloat(parsed2, ctx->alloc); + } + + return xj_newInt(parsed, ctx->alloc); +} + +static xj_value *parseValue(context_t *ctx); + +static xj_value *parseArray(context_t *ctx) +{ + assert(ctx->i < ctx->len && ctx->str[ctx->i] == '['); + + ctx->i += 1; // Skip ']'. + + // Skip whitespace. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an array, right after the first '['"); + return NULL; + } + + if(ctx->str[ctx->i] == ']') /* Empty array */ + return xj_newArray__nocheck(NULL, 0, ctx->alloc); + + xj_value *head = NULL; + xj_value **tail = &head; + int count = 0; + + while(1) + { + xj_value *child = parseValue(ctx); + + if(child == NULL) + return NULL; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an array, right after the %dth child", count+1); + return NULL; + } + + (*tail)->next = child; + *tail = child; + count += 1; + + if(ctx->str[ctx->i] == ']') + break; + + if(ctx->str[ctx->i] != ',') + { + xj_report(ctx->error, "Bad character '%c' inside of an array", ctx->str[ctx->i]); + return NULL; + } + + ctx->i += 1; // Skip ','. + + // Skip whitespace. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an array, right after the ',' after the %dth child", count+1); + return NULL; + } + } + + ctx->i += 1; // Skip ']'. + + return xj_newArray__nocheck(head, count, ctx->alloc); +} + +static xj_value *parseObject(context_t *ctx) +{ + assert(ctx->i < ctx->len && ctx->str[ctx->i] == '{'); + + ctx->i += 1; // Skip '{'. + + // Skip whitespace. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an object, right after the first '{'"); + return NULL; + } + + if(ctx->str[ctx->i] == '}') /* Empty object */ + return xj_newObject__nocheck(NULL, 0, ctx->alloc); + + xj_value *head = NULL; + xj_value **tail = &head; + int count = 0; + + while(1) + { + if(ctx->str[ctx->i] != '"') + { + xj_report(ctx->error, "Bad character '%c' where a string was expected"); + return NULL; + } + + char *key = parseString(ctx, 1); + + if(key == NULL) + return NULL; + + // Skip whitespace before ':'. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an object, right after the %dth child's key", count+1); + return NULL; + } + + if(ctx->str[ctx->i] != ':') + { + xj_report(ctx->error, "Bad character '%c' where ':' was expected"); + return NULL; + } + + ctx->i += 1; // Skip the ':'. + + // Skip whitespace after ':'. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an object, right before the %dth child's value", count+1); + return NULL; + } + + xj_value *child = parseValue(ctx); + + if(child == NULL) + return NULL; + + child->key = key; + + (*tail)->next = child; + *tail = child; + count += 1; + + if(ctx->str[ctx->i] == '}') + break; + + if(ctx->str[ctx->i] != ',') + { + xj_report(ctx->error, "Bad character '%c' inside of an object", ctx->str[ctx->i]); + return NULL; + } + + ctx->i += 1; // Skip ','. + + // Skip whitespace. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an object, right after the ',' after the %dth child", count+1); + return NULL; + } + } + + ctx->i += 1; // Skip '}'. + + return xj_newObject__nocheck(head, count, ctx->alloc); +} + +static xj_value *parseValue(context_t *ctx) +{ + assert(!isspace(ctx->str[ctx->i])); + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended where a value was expected"); + return NULL; + } + + char c = ctx->str[ctx->i]; + + if(c == '"') + return parseString(ctx, 0); + + if(isdigit(c)) + return parseNumber(ctx); + + if(c == '[') + return parseArray(ctx); + + if(c == '{') + return parseObject(ctx); + + static const char kword_null [] = "null"; + static const char kword_true [] = "true"; + static const char kword_false[] = "false"; + const char *kword; + int kwlen; + + if(c == 'n') + { + kword = kword_null; + kwlen = sizeof(kword_null)-1; + } + + if(c == 't') + { + kword = kword_true; + kwlen = sizeof(kword_true)-1; + } + + if(c == 'f') + { + kword = kword_false; + kwlen = sizeof(kword_false)-1; + } + + if(ctx->i + kwlen <= ctx->len && !strncmp(ctx->str + ctx->i, kword, kwlen)) + { + switch(c) + { + case 'n': return xj_newNull(ctx->alloc); + case 't': return xj_newBool(1, ctx->alloc); + case 'f': return xj_newBool(0, ctx->alloc); + } + /* UNREACHABLE */ + } + + if(ctx->i + kwlen > ctx->len) + { + xj_report(ctx->error, "String ended unexpectedly"); + return NULL; + } + + { + int p = 0; + while(kword[p] == ctx->str[ctx->i+p]) + p += 1; + ctx->i += p; + } + + xj_report(ctx->error, "Bad character '%c'", ctx->str[ctx->i]); + return NULL; +} + +xj_value *xj_decode(const char *str, int len, + xj_alloc *alloc, xj_error *error) +{ + if(str == NULL) str = ""; + if(len < 0) len = strlen(str); + + int i = 0; + + // Skip whitespace + while(i < len && isspace(str[i])) + i += 1; + + if(i == len) + { + xj_report(error, "The string only contains whitespace"); + return NULL; + } + + context_t ctx = { + .str = str, .len = len, + .alloc = alloc, .error = error }; + return parseValue(&ctx); +} + +typedef struct bucket_t bucket_t; +struct bucket_t { + bucket_t *next; + char body[4096-sizeof(void*)]; +}; + +typedef struct { + int size, used; + bucket_t *tail, head; +} buffer_t; + +static xj_bool appendString(buffer_t *buff, const char *str, int len) +{ + assert(str != NULL && len >= 0); + + if(buff->used + len > (int) sizeof(buff->tail->body)) + { + bucket_t *buck = malloc(sizeof(bucket_t)); + + if(buck == NULL) + return 0; + + buck->next = NULL; + buff->tail->next = buck; + buff->tail = buck; + buff->used = 0; + } + memcpy(buff->tail->body + buff->used, str, len); + buff->used += len; + buff->size += len; + return 1; +} + +static _Bool encodeString(const char *str, int len, buffer_t *buff) +{ + if(!appendString(buff, "\"", 1)) + return 0; + + if(!appendString(buff, str, len)) + return 0; + + if(!appendString(buff, "\"", 1)) + return 0; + + return 1; +} + +static _Bool encodeValue(xj_value *val, buffer_t *buff) +{ + switch(val == NULL ? XJ_NULL : val->type) + { + case XJ_NULL: + return appendString(buff, "null", 4); + + case XJ_BOOL: + return val->as_bool + ? appendString(buff, "true", 4) + : appendString(buff, "false", 5); + + case XJ_INT: + { + char temp[32]; + int k = snprintf(temp, sizeof(temp), + "%lld", val->as_int); + assert(k >= 0 && k < (int) sizeof(temp)); + if(!appendString(buff, temp, k)) + return 0; + break; + } + + case XJ_FLOAT: + { + char temp[32]; + int k = snprintf(temp, sizeof(temp), + "%g", val->as_float); + assert(k >= 0 && k < (int) sizeof(temp)); + if(!appendString(buff, temp, k)) + return 0; + break; + } + + case XJ_ARRAY: + { + if(!appendString(buff, "[", 1)) + return 0; + + xj_value *child = val->as_object; + while(child != NULL) + { + if(!encodeValue(child, buff)) + return 0; + + child = child->next; + + if(child != NULL) + if(!appendString(buff, ", ", 2)) + return 0; + } + + if(!appendString(buff, "]", 1)) + return 0; + break; + } + + case XJ_OBJECT: + { + if(!appendString(buff, "{", 1)) + return 0; + + xj_value *child = val->as_object; + while(child != NULL) + { + if(!encodeString(child->key, -1, buff)) + return 0; + + if(!appendString(buff, ": ", 2)) + return 0; + + if(!encodeValue(child, buff)) + return 0; + + child = child->next; + + if(child != NULL) + if(!appendString(buff, ", ", 2)) + return 0; + } + + if(!appendString(buff, "}", 1)) + return 0; + break; + } + + case XJ_STRING: + return encodeString(val->as_string, val->size, buff); + } + return 0; +} + +char *xj_encode(xj_value *value, int *len) +{ + buffer_t buff; + buff.size = 0; + buff.used = 0; + buff.tail = &buff.head; + buff.head.next = NULL; + + _Bool failed = encodeValue(value, &buff); + + char *serialized = NULL; + + if(!failed) + { + /* Serialize */ + + serialized = malloc(buff.size+1); + + if(serialized != NULL) + { + int copied = 0; + + bucket_t *curs = &buff.head; + while(curs->next != NULL) + { + memcpy(serialized + copied, + curs->body, sizeof(curs->body)); + + copied += sizeof(curs->body); + curs = curs->next; + } + + memcpy(serialized + copied, + curs->body, buff.used); + + serialized[buff.size] = '\0'; + + if(len) + *len = buff.size; + } + } + + /* Free the buffer */ + bucket_t *curs = buff.head.next; + while(curs != NULL) + { + bucket_t *next = curs->next; + free(curs); + curs = next; + } + + return serialized; +} \ No newline at end of file diff --git a/xjson.h b/xjson.h new file mode 100644 index 0000000..63eccad --- /dev/null +++ b/xjson.h @@ -0,0 +1,56 @@ +typedef double xj_f64; +typedef long long xj_i64; +typedef _Bool xj_bool; + +_Static_assert(sizeof(xj_f64) == 8); +_Static_assert(sizeof(xj_i64) == 8); + +enum { + XJ_NULL, + XJ_BOOL, + XJ_INT, + XJ_FLOAT, + XJ_ARRAY, + XJ_OBJECT, + XJ_STRING, +}; + +typedef struct xj_value xj_value; +struct xj_value { + int type; + int size; + xj_value *next; + char *key; + union { + xj_i64 as_int; + xj_bool as_bool; + xj_f64 as_float; + xj_value *as_array; + xj_value *as_object; + char *as_string; + }; +}; + +typedef struct { + xj_bool occurred; + xj_bool truncated; + char message[128]; +} xj_error; + +typedef struct xj_alloc xj_alloc; +xj_alloc *xj_newAlloc(int size, int ext); +xj_alloc *xj_newAllocUsing(void *mem, int size, int ext, void (*free)(void*)); +void xj_freeAlloc(xj_alloc *alloc); +void *xj_bpMalloc(xj_alloc *alloc, unsigned int size); +xj_value *xj_newNull(xj_alloc *alloc); +xj_value *xj_newBool(xj_bool val, xj_alloc *alloc); +xj_value *xj_newInt(xj_i64 val, xj_alloc *alloc); +xj_value *xj_newFloat(xj_f64 val, xj_alloc *alloc); +xj_value *xj_newArray(xj_value *head, xj_alloc *alloc); +xj_value *xj_newArray__nocheck(xj_value *head, int count, xj_alloc *alloc); +xj_value *xj_newObject(xj_value *head, xj_alloc *alloc); +xj_value *xj_newObject__nocheck(xj_value *head, int count, xj_alloc *alloc); +xj_value *xj_newString(const char *str, int len, xj_alloc *alloc); +char *xj_strdup(const char *str, int len, xj_alloc *alloc); +xj_value *xj_decode(const char *str, int len, xj_alloc *alloc, xj_error *error); +char *xj_encode(xj_value *value, int *len); \ No newline at end of file