#include "cx/json.h"
#include "cx/compare.h"
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <inttypes.h>
static CxJsonValue cx_json_value_nothing = {.type =
CX_JSON_NOTHING};
static int json_cmp_objvalue(
const void *l,
const void *r) {
const CxJsonObjValue *left = l;
const CxJsonObjValue *right = r;
return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name));
}
static CxJsonObjValue *json_find_objvalue(
const CxJsonValue *obj, cxstring name) {
assert(obj->type ==
CX_JSON_OBJECT);
CxJsonObjValue kv_dummy;
kv_dummy.name = cx_mutstrn((
char*) name.ptr, name.length);
size_t index = cx_array_binary_search(
obj->value.object.values,
obj->value.object.values_size,
sizeof(CxJsonObjValue),
&kv_dummy,
json_cmp_objvalue
);
if (index == obj->value.object.values_size) {
return NULL;
}
else {
return &obj->value.object.values[index];
}
}
static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) {
assert(objv->type ==
CX_JSON_OBJECT);
const CxAllocator *
const al = objv->allocator;
CxJsonObject *obj = &(objv->value.object);
size_t index = cx_array_binary_search_sup(
obj->values,
obj->values_size,
sizeof(CxJsonObjValue),
&member, json_cmp_objvalue
);
if (index < obj->values_size &&
0 == json_cmp_objvalue(&member, &obj->values[index])) {
cx_strfree_a(al, &obj->values[index].name);
cxJsonValueFree(obj->values[index].value);
obj->values[index] = member;
return 0;
}
CxArrayReallocator arealloc = cx_array_reallocator(al,
NULL);
size_t oldcap = obj->values_capacity;
if (cx_array_simple_reserve_a(&arealloc, obj->values,
1))
return 1;
size_t newcap = obj->values_capacity;
if (newcap > oldcap) {
if (cxReallocateArray(al, &obj->indices, newcap,
sizeof(
size_t))) {
return 1;
}
}
if (index < obj->values_size) {
memmove(
&obj->values[index+
1],
&obj->values[index],
(obj->values_size - index) *
sizeof(CxJsonObjValue)
);
for (
size_t i =
0; i < obj->values_size ; i++) {
if (obj->indices[i] >= index) {
obj->indices[i]++;
}
}
}
obj->values[index] = member;
obj->indices[obj->values_size] = index;
obj->values_size++;
return 0;
}
static void token_destroy(CxJsonToken *token) {
if (token->allocated) {
cx_strfree(&token->content);
}
}
static int num_isexp(
const char *content,
size_t length,
size_t pos) {
if (pos >= length) {
return 0;
}
int ok =
0;
for (
size_t i = pos; i < length; i++) {
char c = content[i];
if (isdigit(c)) {
ok =
1;
}
else if (i == pos) {
if (!(c ==
'+' || c ==
'-')) {
return 0;
}
}
else {
return 0;
}
}
return ok;
}
static CxJsonTokenType token_numbertype(
const char *content,
size_t length) {
if (length ==
0)
return CX_JSON_TOKEN_ERROR;
if (content[
0] !=
'-' && !isdigit(content[
0])) {
return CX_JSON_TOKEN_ERROR;
}
CxJsonTokenType type =
CX_JSON_TOKEN_INTEGER;
for (
size_t i =
1; i < length; i++) {
if (content[i] ==
'.') {
if (type ==
CX_JSON_TOKEN_NUMBER) {
return CX_JSON_TOKEN_ERROR;
}
type =
CX_JSON_TOKEN_NUMBER;
}
else if (content[i] ==
'e' || content[i] ==
'E') {
return num_isexp(content, length, i +
1) ?
CX_JSON_TOKEN_NUMBER :
CX_JSON_TOKEN_ERROR;
}
else if (!isdigit(content[i])) {
return CX_JSON_TOKEN_ERROR;
}
}
return type;
}
static CxJsonToken token_create(CxJson *json, bool isstring,
size_t start,
size_t end) {
cxmutstr str = cx_mutstrn(json->buffer.space + start, end - start);
bool allocated = false;
if (json->uncompleted.tokentype !=
CX_JSON_NO_TOKEN) {
allocated = true;
str = cx_strcat_m(json->uncompleted.content,
1, str);
if (str.ptr ==
NULL) {
return (CxJsonToken){
CX_JSON_NO_TOKEN, false, {
NULL,
0}};
}
}
json->uncompleted = (CxJsonToken){
0};
CxJsonTokenType ttype;
if (isstring) {
ttype =
CX_JSON_TOKEN_STRING;
}
else {
cxstring s = cx_strcast(str);
if (!cx_strcmp(s,
CX_STR(
"true")) || !cx_strcmp(s,
CX_STR(
"false"))
|| !cx_strcmp(s,
CX_STR(
"null"))) {
ttype =
CX_JSON_TOKEN_LITERAL;
}
else {
ttype = token_numbertype(str.ptr, str.length);
}
}
if (ttype ==
CX_JSON_TOKEN_ERROR) {
if (allocated) {
cx_strfree(&str);
}
return (CxJsonToken){
CX_JSON_TOKEN_ERROR, false, {
NULL,
0}};
}
return (CxJsonToken){ttype, allocated, str};
}
static CxJsonTokenType char2ttype(
char c) {
switch (c) {
case '[': {
return CX_JSON_TOKEN_BEGIN_ARRAY;
}
case '{': {
return CX_JSON_TOKEN_BEGIN_OBJECT;
}
case ']': {
return CX_JSON_TOKEN_END_ARRAY;
}
case '}': {
return CX_JSON_TOKEN_END_OBJECT;
}
case ':': {
return CX_JSON_TOKEN_NAME_SEPARATOR;
}
case ',': {
return CX_JSON_TOKEN_VALUE_SEPARATOR;
}
case '""': {
return CX_JSON_TOKEN_STRING;
}
default: {
if (isspace(c)) {
return CX_JSON_TOKEN_SPACE;
}
}
}
return CX_JSON_NO_TOKEN;
}
static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) {
if (cxBufferEof(&json->buffer)) {
return json->uncompleted.tokentype ==
CX_JSON_NO_TOKEN ?
CX_JSON_NO_DATA :
CX_JSON_INCOMPLETE_DATA;
}
CxJsonTokenType ttype = json->uncompleted.tokentype;
size_t token_start = json->buffer.pos;
for (
size_t i = json->buffer.pos; i < json->buffer.size; i++) {
char c = json->buffer.space[i];
if (ttype !=
CX_JSON_TOKEN_STRING) {
CxJsonTokenType ctype = char2ttype(c);
if (ttype ==
CX_JSON_NO_TOKEN) {
if (ctype ==
CX_JSON_TOKEN_SPACE) {
json->buffer.pos++;
continue;
}
else if (ctype ==
CX_JSON_TOKEN_STRING) {
ttype =
CX_JSON_TOKEN_STRING;
token_start = i;
}
else if (ctype !=
CX_JSON_NO_TOKEN) {
json->buffer.pos = i +
1;
*result = (CxJsonToken){ctype, false, {
NULL,
0}};
return CX_JSON_NO_ERROR;
}
else {
ttype =
CX_JSON_TOKEN_LITERAL;
token_start = i;
}
}
else {
if (ctype !=
CX_JSON_NO_TOKEN) {
*result = token_create(json, false, token_start, i);
if (result->tokentype ==
CX_JSON_NO_TOKEN) {
return CX_JSON_BUFFER_ALLOC_FAILED;
}
if (result->tokentype ==
CX_JSON_TOKEN_ERROR) {
return CX_JSON_FORMAT_ERROR_NUMBER;
}
json->buffer.pos = i;
return CX_JSON_NO_ERROR;
}
}
}
else {
if (json->tokenizer_escape) {
json->tokenizer_escape = false;
}
else {
if (c ==
'""') {
*result = token_create(json, true, token_start, i +
1);
if (result->tokentype ==
CX_JSON_NO_TOKEN) {
return CX_JSON_BUFFER_ALLOC_FAILED;
}
json->buffer.pos = i +
1;
return CX_JSON_NO_ERROR;
}
else if (c ==
'\\') {
json->tokenizer_escape = true;
}
}
}
}
if (ttype !=
CX_JSON_NO_TOKEN) {
size_t uncompleted_len = json->buffer.size - token_start;
if (json->uncompleted.tokentype ==
CX_JSON_NO_TOKEN) {
CxJsonToken uncompleted = {
ttype, true,
cx_strdup(cx_strn(json->buffer.space + token_start, uncompleted_len))
};
if (uncompleted.content.ptr ==
NULL) {
return CX_JSON_BUFFER_ALLOC_FAILED;
}
json->uncompleted = uncompleted;
}
else {
assert(json->uncompleted.allocated);
cxmutstr str = cx_strcat_m(json->uncompleted.content,
1,
cx_strn(json->buffer.space + token_start, uncompleted_len));
if (str.ptr ==
NULL) {
return CX_JSON_BUFFER_ALLOC_FAILED;
}
json->uncompleted.content = str;
}
json->buffer.pos += uncompleted_len;
}
return CX_JSON_INCOMPLETE_DATA;
}
static cxmutstr unescape_string(
const CxAllocator *a, cxmutstr str) {
cxmutstr result;
result.length =
0;
result.ptr = cxMalloc(a, str.length -
1);
if (result.ptr ==
NULL)
return result;
bool u = false;
for (
size_t i =
1; i < str.length -
1; i++) {
char c = str.ptr[i];
if (u) {
u = false;
if (c ==
'n') {
c =
'\n';
}
else if (c ==
't') {
c =
'\t';
}
result.ptr[result.length++] = c;
}
else {
if (c ==
'\\') {
u = true;
}
else {
result.ptr[result.length++] = c;
}
}
}
result.ptr[result.length] =
0;
return result;
}
static CxJsonValue* create_json_value(CxJson *json, CxJsonValueType type) {
CxJsonValue *v = cxCalloc(json->allocator,
1,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->type = type;
v->allocator = json->allocator;
if (type ==
CX_JSON_ARRAY) {
cx_array_initialize_a(json->allocator, v->value.array.array,
16);
if (v->value.array.array ==
NULL)
goto create_json_value_exit_error;
}
else if (type ==
CX_JSON_OBJECT) {
cx_array_initialize_a(json->allocator, v->value.object.values,
16);
v->value.object.indices = cxCalloc(json->allocator,
16,
sizeof(
size_t));
if (v->value.object.values ==
NULL ||
v->value.object.indices ==
NULL)
goto create_json_value_exit_error;
}
if (json->vbuf_size >
0) {
CxJsonValue *parent = json->vbuf[json->vbuf_size -
1];
assert(parent !=
NULL);
if (parent->type ==
CX_JSON_ARRAY) {
CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator,
NULL);
if (cx_array_simple_add_a(&value_realloc, parent->value.array.array, v)) {
goto create_json_value_exit_error;
}
}
else if (parent->type ==
CX_JSON_OBJECT) {
assert(json->uncompleted_member.name.ptr !=
NULL);
json->uncompleted_member.value = v;
if (json_add_objvalue(parent, json->uncompleted_member)) {
goto create_json_value_exit_error;
}
json->uncompleted_member.name = (cxmutstr) {
NULL,
0};
}
else {
assert(false);
}
}
if (type ==
CX_JSON_ARRAY || type ==
CX_JSON_OBJECT) {
CxArrayReallocator vbuf_realloc = cx_array_reallocator(
NULL, json->vbuf_internal);
if (cx_array_simple_add_a(&vbuf_realloc, json->vbuf, v)) {
goto create_json_value_exit_error;
}
}
if (json->parsed ==
NULL) {
json->parsed = v;
}
return v;
create_json_value_exit_error:
cxJsonValueFree(v);
return NULL;
}
#define JP_STATE_VALUE_BEGIN 0
#define JP_STATE_VALUE_END 10
#define JP_STATE_VALUE_BEGIN_OBJ 1
#define JP_STATE_OBJ_SEP_OR_CLOSE 11
#define JP_STATE_VALUE_BEGIN_AR 2
#define JP_STATE_ARRAY_SEP_OR_CLOSE 12
#define JP_STATE_OBJ_NAME_OR_CLOSE 5
#define JP_STATE_OBJ_NAME 6
#define JP_STATE_OBJ_COLON 7
void cxJsonInit(CxJson *json,
const CxAllocator *allocator) {
if (allocator ==
NULL) {
allocator = cxDefaultAllocator;
}
memset(json,
0,
sizeof(CxJson));
json->allocator = allocator;
json->states = json->states_internal;
json->states_capacity = cx_nmemb(json->states_internal);
json->states[
0] =
JP_STATE_VALUE_BEGIN;
json->states_size =
1;
json->vbuf = json->vbuf_internal;
json->vbuf_capacity = cx_nmemb(json->vbuf_internal);
}
void cxJsonDestroy(CxJson *json) {
cxBufferDestroy(&json->buffer);
if (json->states != json->states_internal) {
free(json->states);
}
if (json->vbuf != json->vbuf_internal) {
free(json->vbuf);
}
cxJsonValueFree(json->parsed);
json->parsed =
NULL;
if (json->uncompleted_member.name.ptr !=
NULL) {
cx_strfree_a(json->allocator, &json->uncompleted_member.name);
json->uncompleted_member = (CxJsonObjValue){{
NULL,
0},
NULL};
}
}
int cxJsonFilln(CxJson *json,
const char *buf,
size_t size) {
if (cxBufferEof(&json->buffer)) {
cxBufferDestroy(&json->buffer);
cxBufferInit(&json->buffer, (
char*) buf, size,
NULL,
CX_BUFFER_AUTO_EXTEND |
CX_BUFFER_COPY_ON_WRITE);
json->buffer.size = size;
return 0;
}
else {
return size != cxBufferAppend(buf,
1, size, &json->buffer);
}
}
static void json_add_state(CxJson *json,
int state) {
json->states[json->states_size++] = state;
}
#define return_rec(code) \
token_destroy(&token); \
return code
static enum cx_json_status json_parse(CxJson *json) {
CxJsonValue *vbuf =
NULL;
CxJsonToken token;
{
enum cx_json_status ret = token_parse_next(json, &token);
if (ret !=
CX_JSON_NO_ERROR) {
return ret;
}
}
assert(json->states_size >
0);
int state = json->states[--json->states_size];
CxArrayReallocator state_realloc = cx_array_reallocator(
NULL, json->states_internal);
if (cx_array_simple_reserve_a(&state_realloc, json->states,
2)) {
return CX_JSON_BUFFER_ALLOC_FAILED;
}
if (state <
3) {
json_add_state(json,
10 + state);
switch (token.tokentype) {
case CX_JSON_TOKEN_BEGIN_ARRAY: {
if (create_json_value(json,
CX_JSON_ARRAY) ==
NULL) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
json_add_state(json,
JP_STATE_VALUE_BEGIN_AR);
return_rec(
CX_JSON_NO_ERROR);
}
case CX_JSON_TOKEN_BEGIN_OBJECT: {
if (create_json_value(json,
CX_JSON_OBJECT) ==
NULL) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
json_add_state(json,
JP_STATE_OBJ_NAME_OR_CLOSE);
return_rec(
CX_JSON_NO_ERROR);
}
case CX_JSON_TOKEN_STRING: {
if ((vbuf = create_json_value(json,
CX_JSON_STRING)) ==
NULL) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
cxmutstr str = unescape_string(json->allocator, token.content);
if (str.ptr ==
NULL) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
vbuf->value.string = str;
return_rec(
CX_JSON_NO_ERROR);
}
case CX_JSON_TOKEN_INTEGER:
case CX_JSON_TOKEN_NUMBER: {
int type = token.tokentype ==
CX_JSON_TOKEN_INTEGER ?
CX_JSON_INTEGER :
CX_JSON_NUMBER;
if (
NULL == (vbuf = create_json_value(json, type))) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
if (type ==
CX_JSON_INTEGER) {
if (cx_strtoi64(token.content, &vbuf->value.integer,
10)) {
return_rec(
CX_JSON_FORMAT_ERROR_NUMBER);
}
}
else {
if (cx_strtod(token.content, &vbuf->value.number)) {
return_rec(
CX_JSON_FORMAT_ERROR_NUMBER);
}
}
return_rec(
CX_JSON_NO_ERROR);
}
case CX_JSON_TOKEN_LITERAL: {
if ((vbuf = create_json_value(json,
CX_JSON_LITERAL)) ==
NULL) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
if (
0 == cx_strcmp(cx_strcast(token.content), cx_str(
"true"))) {
vbuf->value.literal =
CX_JSON_TRUE;
}
else if (
0 == cx_strcmp(cx_strcast(token.content), cx_str(
"false"))) {
vbuf->value.literal =
CX_JSON_FALSE;
}
else {
vbuf->value.literal =
CX_JSON_NULL;
}
return_rec(
CX_JSON_NO_ERROR);
}
default: {
return_rec(
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
}
}
}
else if (state ==
JP_STATE_ARRAY_SEP_OR_CLOSE) {
if (token.tokentype ==
CX_JSON_TOKEN_VALUE_SEPARATOR) {
json_add_state(json,
JP_STATE_VALUE_BEGIN_AR);
return_rec(
CX_JSON_NO_ERROR);
}
else if (token.tokentype ==
CX_JSON_TOKEN_END_ARRAY) {
json->vbuf_size--;
return_rec(
CX_JSON_NO_ERROR);
}
else {
return_rec(
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
}
}
else if (state ==
JP_STATE_OBJ_NAME_OR_CLOSE || state ==
JP_STATE_OBJ_NAME) {
if (state ==
JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype ==
CX_JSON_TOKEN_END_OBJECT) {
json->vbuf_size--;
return_rec(
CX_JSON_NO_ERROR);
}
else {
if (token.tokentype !=
CX_JSON_TOKEN_STRING) {
return_rec(
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
}
cxmutstr name = unescape_string(json->allocator, token.content);
if (name.ptr ==
NULL) {
return_rec(
CX_JSON_VALUE_ALLOC_FAILED);
}
assert(json->uncompleted_member.name.ptr ==
NULL);
json->uncompleted_member.name = name;
assert(json->vbuf_size >
0);
json_add_state(json,
JP_STATE_OBJ_COLON);
return_rec(
CX_JSON_NO_ERROR);
}
}
else if (state ==
JP_STATE_OBJ_COLON) {
if (token.tokentype !=
CX_JSON_TOKEN_NAME_SEPARATOR) {
return_rec(
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
}
json_add_state(json,
JP_STATE_VALUE_BEGIN_OBJ);
return_rec(
CX_JSON_NO_ERROR);
}
else if (state ==
JP_STATE_OBJ_SEP_OR_CLOSE) {
if (token.tokentype ==
CX_JSON_TOKEN_VALUE_SEPARATOR) {
json_add_state(json,
JP_STATE_OBJ_NAME);
return_rec(
CX_JSON_NO_ERROR);
}
else if (token.tokentype ==
CX_JSON_TOKEN_END_OBJECT) {
json->vbuf_size--;
return_rec(
CX_JSON_NO_ERROR);
}
else {
return_rec(
CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
}
}
else {
assert(false);
return_rec(-
1);
}
}
CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) {
if (json->buffer.space ==
NULL) {
return CX_JSON_NULL_DATA;
}
*value = &cx_json_value_nothing;
CxJsonStatus result;
do {
result = json_parse(json);
if (result ==
CX_JSON_NO_ERROR && json->states_size ==
1) {
assert(json->states[
0] ==
JP_STATE_VALUE_END);
assert(json->vbuf_size ==
0);
*value = json->parsed;
json->parsed =
NULL;
json->states[
0] =
JP_STATE_VALUE_BEGIN;
return CX_JSON_NO_ERROR;
}
}
while (result ==
CX_JSON_NO_ERROR);
if (result ==
CX_JSON_NO_DATA && json->states_size >
1) {
return CX_JSON_INCOMPLETE_DATA;
}
return result;
}
void cxJsonValueFree(CxJsonValue *value) {
if (value ==
NULL || value->type ==
CX_JSON_NOTHING)
return;
switch (value->type) {
case CX_JSON_OBJECT: {
CxJsonObject obj = value->value.object;
for (
size_t i =
0; i < obj.values_size; i++) {
cxJsonValueFree(obj.values[i].value);
cx_strfree_a(value->allocator, &obj.values[i].name);
}
cxFree(value->allocator, obj.values);
cxFree(value->allocator, obj.indices);
break;
}
case CX_JSON_ARRAY: {
CxJsonArray array = value->value.array;
for (
size_t i =
0; i < array.array_size; i++) {
cxJsonValueFree(array.array[i]);
}
cxFree(value->allocator, array.array);
break;
}
case CX_JSON_STRING: {
cxFree(value->allocator, value->value.string.ptr);
break;
}
default: {
break;
}
}
cxFree(value->allocator, value);
}
CxJsonValue* cxJsonCreateObj(
const CxAllocator* allocator) {
CxJsonValue* v = cxMalloc(allocator,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->allocator = allocator;
v->type =
CX_JSON_OBJECT;
cx_array_initialize_a(allocator, v->value.object.values,
16);
if (v->value.object.values ==
NULL) {
cxFree(allocator, v);
return NULL;
}
v->value.object.indices = cxCalloc(allocator,
16,
sizeof(
size_t));
if (v->value.object.indices ==
NULL) {
cxFree(allocator, v->value.object.values);
cxFree(allocator, v);
return NULL;
}
return v;
}
CxJsonValue* cxJsonCreateArr(
const CxAllocator* allocator) {
CxJsonValue* v = cxMalloc(allocator,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->allocator = allocator;
v->type =
CX_JSON_ARRAY;
cx_array_initialize_a(allocator, v->value.array.array,
16);
if (v->value.array.array ==
NULL) { cxFree(allocator, v);
return NULL; }
return v;
}
CxJsonValue* cxJsonCreateNumber(
const CxAllocator* allocator,
double num) {
CxJsonValue* v = cxMalloc(allocator,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->allocator = allocator;
v->type =
CX_JSON_NUMBER;
v->value.number = num;
return v;
}
CxJsonValue* cxJsonCreateInteger(
const CxAllocator* allocator,
int64_t num) {
CxJsonValue* v = cxMalloc(allocator,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->allocator = allocator;
v->type =
CX_JSON_INTEGER;
v->value.integer = num;
return v;
}
CxJsonValue* cxJsonCreateString(
const CxAllocator* allocator,
const char* str) {
return cxJsonCreateCxString(allocator, cx_str(str));
}
CxJsonValue* cxJsonCreateCxString(
const CxAllocator* allocator, cxstring str) {
CxJsonValue* v = cxMalloc(allocator,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->allocator = allocator;
v->type =
CX_JSON_STRING;
cxmutstr s = cx_strdup_a(allocator, str);
if (s.ptr ==
NULL) { cxFree(allocator, v);
return NULL; }
v->value.string = s;
return v;
}
CxJsonValue* cxJsonCreateLiteral(
const CxAllocator* allocator, CxJsonLiteral lit) {
CxJsonValue* v = cxMalloc(allocator,
sizeof(CxJsonValue));
if (v ==
NULL)
return NULL;
v->allocator = allocator;
v->type =
CX_JSON_LITERAL;
v->value.literal = lit;
return v;
}
static void cx_json_arr_free_temp(CxJsonValue** values,
size_t count) {
for (
size_t i =
0; i < count; i++) {
if (values[i] ==
NULL)
break;
cxJsonValueFree(values[i]);
}
free(values);
}
int cxJsonArrAddNumbers(CxJsonValue* arr,
const double* num,
size_t count) {
CxJsonValue** values = calloc(count,
sizeof(CxJsonValue*));
if (values ==
NULL)
return -
1;
for (
size_t i =
0; i < count; i++) {
values[i] = cxJsonCreateNumber(arr->allocator, num[i]);
if (values[i] ==
NULL) { cx_json_arr_free_temp(values, count);
return -
1; }
}
int ret = cxJsonArrAddValues(arr, values, count);
free(values);
return ret;
}
int cxJsonArrAddIntegers(CxJsonValue* arr,
const int64_t* num,
size_t count) {
CxJsonValue** values = calloc(count,
sizeof(CxJsonValue*));
if (values ==
NULL)
return -
1;
for (
size_t i =
0; i < count; i++) {
values[i] = cxJsonCreateInteger(arr->allocator, num[i]);
if (values[i] ==
NULL) { cx_json_arr_free_temp(values, count);
return -
1; }
}
int ret = cxJsonArrAddValues(arr, values, count);
free(values);
return ret;
}
int cxJsonArrAddStrings(CxJsonValue* arr,
const char*
const* str,
size_t count) {
CxJsonValue** values = calloc(count,
sizeof(CxJsonValue*));
if (values ==
NULL)
return -
1;
for (
size_t i =
0; i < count; i++) {
values[i] = cxJsonCreateString(arr->allocator, str[i]);
if (values[i] ==
NULL) { cx_json_arr_free_temp(values, count);
return -
1; }
}
int ret = cxJsonArrAddValues(arr, values, count);
free(values);
return ret;
}
int cxJsonArrAddCxStrings(CxJsonValue* arr,
const cxstring* str,
size_t count) {
CxJsonValue** values = calloc(count,
sizeof(CxJsonValue*));
if (values ==
NULL)
return -
1;
for (
size_t i =
0; i < count; i++) {
values[i] = cxJsonCreateCxString(arr->allocator, str[i]);
if (values[i] ==
NULL) { cx_json_arr_free_temp(values, count);
return -
1; }
}
int ret = cxJsonArrAddValues(arr, values, count);
free(values);
return ret;
}
int cxJsonArrAddLiterals(CxJsonValue* arr,
const CxJsonLiteral* lit,
size_t count) {
CxJsonValue** values = calloc(count,
sizeof(CxJsonValue*));
if (values ==
NULL)
return -
1;
for (
size_t i =
0; i < count; i++) {
values[i] = cxJsonCreateLiteral(arr->allocator, lit[i]);
if (values[i] ==
NULL) { cx_json_arr_free_temp(values, count);
return -
1; }
}
int ret = cxJsonArrAddValues(arr, values, count);
free(values);
return ret;
}
int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue*
const* val,
size_t count) {
CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator,
NULL);
assert(arr->type ==
CX_JSON_ARRAY);
return cx_array_simple_copy_a(&value_realloc,
arr->value.array.array,
arr->value.array.array_size,
val, count
);
}
int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child) {
cxmutstr k = cx_strdup_a(obj->allocator, name);
if (k.ptr ==
NULL)
return -
1;
CxJsonObjValue kv = {k, child};
if (json_add_objvalue(obj, kv)) {
cx_strfree_a(obj->allocator, &k);
return 1;
}
else {
return 0;
}
}
CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name) {
CxJsonValue* v = cxJsonCreateObj(obj->allocator);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL; }
return v;
}
CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name) {
CxJsonValue* v = cxJsonCreateArr(obj->allocator);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL; }
return v;
}
CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name,
double num) {
CxJsonValue* v = cxJsonCreateNumber(obj->allocator, num);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL; }
return v;
}
CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name,
int64_t num) {
CxJsonValue* v = cxJsonCreateInteger(obj->allocator, num);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL; }
return v;
}
CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name,
const char* str) {
CxJsonValue* v = cxJsonCreateString(obj->allocator, str);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL; }
return v;
}
CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str) {
CxJsonValue* v = cxJsonCreateCxString(obj->allocator, str);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL; }
return v;
}
CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) {
CxJsonValue* v = cxJsonCreateLiteral(obj->allocator, lit);
if (v ==
NULL)
return NULL;
if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v);
return NULL;}
return v;
}
CxJsonValue *cxJsonArrGet(
const CxJsonValue *value,
size_t index) {
if (index >= value->value.array.array_size) {
return &cx_json_value_nothing;
}
return value->value.array.array[index];
}
CxIterator cxJsonArrIter(
const CxJsonValue *value) {
return cxIteratorPtr(
value->value.array.array,
value->value.array.array_size
);
}
CxIterator cxJsonObjIter(
const CxJsonValue *value) {
return cxIterator(
value->value.object.values,
sizeof(CxJsonObjValue),
value->value.object.values_size
);
}
CxJsonValue *cx_json_obj_get_cxstr(
const CxJsonValue *value, cxstring name) {
CxJsonObjValue *member = json_find_objvalue(value, name);
if (member ==
NULL) {
return &cx_json_value_nothing;
}
else {
return member->value;
}
}
static const CxJsonWriter cx_json_writer_default = {
false,
true,
255,
false,
4
};
CxJsonWriter cxJsonWriterCompact(
void) {
return cx_json_writer_default;
}
CxJsonWriter cxJsonWriterPretty(bool use_spaces) {
return (CxJsonWriter) {
true,
true,
255,
use_spaces,
4
};
}
static int cx_json_writer_indent(
void *target,
cx_write_func wfunc,
const CxJsonWriter *settings,
unsigned int depth
) {
if (depth ==
0)
return 0;
const char* indent;
size_t width = depth;
if (settings->indent_space) {
if (settings->indent ==
0)
return 0;
width *= settings->indent;
indent =
" ";
}
else {
indent =
"\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
}
size_t full = width /
32;
size_t remaining = width %
32;
for (
size_t i =
0; i < full; i++) {
if (
32 != wfunc(indent,
1,
32, target))
return 1;
}
if (remaining != wfunc(indent,
1, remaining, target))
return 1;
return 0;
}
int cx_json_write_rec(
void *target,
const CxJsonValue *value,
cx_write_func wfunc,
const CxJsonWriter *settings,
unsigned int depth
) {
size_t actual =
0, expected =
0;
char numbuf[
32];
switch (value->type) {
case CX_JSON_OBJECT: {
const char *begin_obj =
"{\n";
if (settings->pretty) {
actual += wfunc(begin_obj,
1,
2, target);
expected +=
2;
}
else {
actual += wfunc(begin_obj,
1,
1, target);
expected++;
}
depth++;
size_t elem_count = value->value.object.values_size;
for (
size_t look_idx =
0; look_idx < elem_count; look_idx++) {
size_t elem_idx = settings->sort_members
? look_idx
: value->value.object.indices[look_idx];
CxJsonObjValue *member = &value->value.object.values[elem_idx];
if (settings->sort_members) {
depth++;depth--;
}
if (settings->pretty) {
if (cx_json_writer_indent(target, wfunc, settings, depth)) {
return 1;
}
}
actual += wfunc(
"\"",
1,
1, target);
actual += wfunc(member->name.ptr,
1,
member->name.length, target);
actual += wfunc(
"\"",
1,
1, target);
const char *obj_name_sep =
": ";
if (settings->pretty) {
actual += wfunc(obj_name_sep,
1,
2, target);
expected +=
4 + member->name.length;
}
else {
actual += wfunc(obj_name_sep,
1,
1, target);
expected +=
3 + member->name.length;
}
if (cx_json_write_rec(target, member->value, wfunc, settings, depth))
return 1;
if (look_idx < elem_count -
1) {
const char *obj_value_sep =
",\n";
if (settings->pretty) {
actual += wfunc(obj_value_sep,
1,
2, target);
expected +=
2;
}
else {
actual += wfunc(obj_value_sep,
1,
1, target);
expected++;
}
}
else {
if (settings->pretty) {
actual += wfunc(
"\n",
1,
1, target);
expected ++;
}
}
}
depth--;
if (settings->pretty) {
if (cx_json_writer_indent(target, wfunc, settings, depth))
return 1;
}
actual += wfunc(
"}",
1,
1, target);
expected++;
break;
}
case CX_JSON_ARRAY: {
actual += wfunc(
"[",
1,
1, target);
expected++;
CxIterator iter = cxJsonArrIter(value);
cx_foreach(CxJsonValue*, element, iter) {
if (cx_json_write_rec(
target, element,
wfunc, settings, depth)
)
return 1;
if (iter.index < iter.elem_count -
1) {
const char *arr_value_sep =
", ";
if (settings->pretty) {
actual += wfunc(arr_value_sep,
1,
2, target);
expected +=
2;
}
else {
actual += wfunc(arr_value_sep,
1,
1, target);
expected++;
}
}
}
actual += wfunc(
"]",
1,
1, target);
expected++;
break;
}
case CX_JSON_STRING: {
actual += wfunc(
"\"",
1,
1, target);
actual += wfunc(value->value.string.ptr,
1,
value->value.string.length, target);
actual += wfunc(
"\"",
1,
1, target);
expected +=
2 + value->value.string.length;
break;
}
case CX_JSON_NUMBER: {
snprintf(numbuf,
32,
"%g", value->value.number);
size_t len = strlen(numbuf);
actual += wfunc(numbuf,
1, len, target);
expected += len;
break;
}
case CX_JSON_INTEGER: {
snprintf(numbuf,
32,
"%" PRIi64, value->value.integer);
size_t len = strlen(numbuf);
actual += wfunc(numbuf,
1, len, target);
expected += len;
break;
}
case CX_JSON_LITERAL: {
if (value->value.literal ==
CX_JSON_TRUE) {
actual += wfunc(
"true",
1,
4, target);
expected +=
4;
}
else if (value->value.literal ==
CX_JSON_FALSE) {
actual += wfunc(
"false",
1,
5, target);
expected +=
5;
}
else {
actual += wfunc(
"null",
1,
4, target);
expected +=
4;
}
break;
}
case CX_JSON_NOTHING: {
break;
}
default: assert(false);
}
return expected != actual;
}
int cxJsonWrite(
void *target,
const CxJsonValue *value,
cx_write_func wfunc,
const CxJsonWriter *settings
) {
if (settings ==
NULL) {
settings = &cx_json_writer_default;
}
assert(target !=
NULL);
assert(value !=
NULL);
assert(wfunc !=
NULL);
return cx_json_write_rec(target, value, wfunc, settings,
0);
}