UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "cx/json.h" 30 31 #include <string.h> 32 #include <assert.h> 33 #include <stdio.h> 34 #include <inttypes.h> 35 36 /* 37 * RFC 8259 38 * https://tools.ietf.org/html/rfc8259 39 */ 40 41 static CxJsonValue cx_json_value_nothing = {.type = CX_JSON_NOTHING}; 42 43 static int json_cmp_objvalue(const void *l, const void *r) { 44 const CxJsonObjValue *left = l; 45 const CxJsonObjValue *right = r; 46 return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name)); 47 } 48 49 static CxJsonObjValue *json_find_objvalue(const CxJsonValue *obj, cxstring name) { 50 assert(obj->type == CX_JSON_OBJECT); 51 CxJsonObjValue kv_dummy; 52 kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length); 53 size_t index = cx_array_binary_search( 54 obj->value.object.values, 55 obj->value.object.values_size, 56 sizeof(CxJsonObjValue), 57 &kv_dummy, 58 json_cmp_objvalue 59 ); 60 if (index == obj->value.object.values_size) { 61 return NULL; 62 } else { 63 return &obj->value.object.values[index]; 64 } 65 } 66 67 static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) { 68 assert(objv->type == CX_JSON_OBJECT); 69 const CxAllocator * const al = objv->allocator; 70 CxJsonObject *obj = &(objv->value.object); 71 72 // determine the index where we need to insert the new member 73 size_t index = cx_array_binary_search_sup( 74 obj->values, 75 obj->values_size, 76 sizeof(CxJsonObjValue), 77 &member, json_cmp_objvalue 78 ); 79 80 // is the name already present? 81 if (index < obj->values_size && 0 == json_cmp_objvalue(&member, &obj->values[index])) { 82 // free the original value 83 cx_strfree_a(al, &obj->values[index].name); 84 cxJsonValueFree(obj->values[index].value); 85 // replace the item 86 obj->values[index] = member; 87 88 // nothing more to do 89 return 0; 90 } 91 92 // determine the old capacity and reserve for one more element 93 CxArrayReallocator arealloc = cx_array_reallocator(al, NULL); 94 size_t oldcap = obj->values_capacity; 95 if (cx_array_simple_reserve_a(&arealloc, obj->values, 1)) return 1; 96 97 // check the new capacity, if we need to realloc the index array 98 size_t newcap = obj->values_capacity; 99 if (newcap > oldcap) { 100 if (cxReallocateArray(al, &obj->indices, newcap, sizeof(size_t))) { 101 return 1; 102 } 103 } 104 105 // check if append or insert 106 if (index < obj->values_size) { 107 // move the other elements 108 memmove( 109 &obj->values[index+1], 110 &obj->values[index], 111 (obj->values_size - index) * sizeof(CxJsonObjValue) 112 ); 113 // increase indices for the moved elements 114 for (size_t i = 0; i < obj->values_size ; i++) { 115 if (obj->indices[i] >= index) { 116 obj->indices[i]++; 117 } 118 } 119 } 120 121 // insert the element and set the index 122 obj->values[index] = member; 123 obj->indices[obj->values_size] = index; 124 obj->values_size++; 125 126 return 0; 127 } 128 129 static void token_destroy(CxJsonToken *token) { 130 if (token->allocated) { 131 cx_strfree(&token->content); 132 } 133 } 134 135 static bool json_isdigit(char c) { 136 // TODO: remove once UCX has public API for this 137 return c >= '0' && c <= '9'; 138 } 139 140 static bool json_isspace(char c) { 141 // TODO: remove once UCX has public API for this 142 return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f'; 143 } 144 145 static int num_isexp(const char *content, size_t length, size_t pos) { 146 if (pos >= length) { 147 return 0; 148 } 149 150 int ok = 0; 151 for (size_t i = pos; i < length; i++) { 152 char c = content[i]; 153 if (json_isdigit(c)) { 154 ok = 1; 155 } else if (i == pos) { 156 if (!(c == '+' || c == '-')) { 157 return 0; 158 } 159 } else { 160 return 0; 161 } 162 } 163 164 return ok; 165 } 166 167 static CxJsonTokenType token_numbertype(const char *content, size_t length) { 168 if (length == 0) return CX_JSON_TOKEN_ERROR; 169 170 if (content[0] != '-' && !json_isdigit(content[0])) { 171 return CX_JSON_TOKEN_ERROR; 172 } 173 174 CxJsonTokenType type = CX_JSON_TOKEN_INTEGER; 175 for (size_t i = 1; i < length; i++) { 176 if (content[i] == '.') { 177 if (type == CX_JSON_TOKEN_NUMBER) { 178 return CX_JSON_TOKEN_ERROR; // more than one decimal separator 179 } 180 type = CX_JSON_TOKEN_NUMBER; 181 } else if (content[i] == 'e' || content[i] == 'E') { 182 return num_isexp(content, length, i + 1) ? CX_JSON_TOKEN_NUMBER : CX_JSON_TOKEN_ERROR; 183 } else if (!json_isdigit(content[i])) { 184 return CX_JSON_TOKEN_ERROR; // char is not a digit, decimal separator or exponent sep 185 } 186 } 187 188 return type; 189 } 190 191 static CxJsonToken token_create(CxJson *json, bool isstring, size_t start, size_t end) { 192 cxmutstr str = cx_mutstrn(json->buffer.space + start, end - start); 193 bool allocated = false; 194 if (json->uncompleted.tokentype != CX_JSON_NO_TOKEN) { 195 allocated = true; 196 str = cx_strcat_m(json->uncompleted.content, 1, str); 197 if (str.ptr == NULL) { // LCOV_EXCL_START 198 return (CxJsonToken){CX_JSON_NO_TOKEN, false, {NULL, 0}}; 199 } // LCOV_EXCL_STOP 200 } 201 json->uncompleted = (CxJsonToken){0}; 202 CxJsonTokenType ttype; 203 if (isstring) { 204 ttype = CX_JSON_TOKEN_STRING; 205 } else { 206 cxstring s = cx_strcast(str); 207 if (!cx_strcmp(s, CX_STR("true")) || !cx_strcmp(s, CX_STR("false")) 208 || !cx_strcmp(s, CX_STR("null"))) { 209 ttype = CX_JSON_TOKEN_LITERAL; 210 } else { 211 ttype = token_numbertype(str.ptr, str.length); 212 } 213 } 214 if (ttype == CX_JSON_TOKEN_ERROR) { 215 if (allocated) { 216 cx_strfree(&str); 217 } 218 return (CxJsonToken){CX_JSON_TOKEN_ERROR, false, {NULL, 0}}; 219 } 220 return (CxJsonToken){ttype, allocated, str}; 221 } 222 223 static CxJsonTokenType char2ttype(char c) { 224 switch (c) { 225 case '[': { 226 return CX_JSON_TOKEN_BEGIN_ARRAY; 227 } 228 case '{': { 229 return CX_JSON_TOKEN_BEGIN_OBJECT; 230 } 231 case ']': { 232 return CX_JSON_TOKEN_END_ARRAY; 233 } 234 case '}': { 235 return CX_JSON_TOKEN_END_OBJECT; 236 } 237 case ':': { 238 return CX_JSON_TOKEN_NAME_SEPARATOR; 239 } 240 case ',': { 241 return CX_JSON_TOKEN_VALUE_SEPARATOR; 242 } 243 case '""': { 244 return CX_JSON_TOKEN_STRING; 245 } 246 default: { 247 if (json_isspace(c)) { 248 return CX_JSON_TOKEN_SPACE; 249 } 250 } 251 } 252 return CX_JSON_NO_TOKEN; 253 } 254 255 static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) { 256 // check if there is data in the buffer 257 if (cxBufferEof(&json->buffer)) { 258 return json->uncompleted.tokentype == CX_JSON_NO_TOKEN ? 259 CX_JSON_NO_DATA : CX_JSON_INCOMPLETE_DATA; 260 } 261 262 // current token type and start index 263 CxJsonTokenType ttype = json->uncompleted.tokentype; 264 size_t token_part_start = json->buffer.pos; 265 266 bool escape_end_of_string = ttype == CX_JSON_TOKEN_STRING 267 && json->uncompleted.content.ptr[json->uncompleted.content.length-1] == '\\'; 268 269 for (size_t i = json->buffer.pos; i < json->buffer.size; i++) { 270 char c = json->buffer.space[i]; 271 if (ttype != CX_JSON_TOKEN_STRING) { 272 // currently non-string token 273 CxJsonTokenType ctype = char2ttype(c); // start of new token? 274 if (ttype == CX_JSON_NO_TOKEN) { 275 if (ctype == CX_JSON_TOKEN_SPACE) { 276 json->buffer.pos++; 277 continue; 278 } else if (ctype == CX_JSON_TOKEN_STRING) { 279 // begin string 280 ttype = CX_JSON_TOKEN_STRING; 281 token_part_start = i; 282 } else if (ctype != CX_JSON_NO_TOKEN) { 283 // single-char token 284 json->buffer.pos = i + 1; 285 *result = (CxJsonToken){ctype, false, {NULL, 0}}; 286 return CX_JSON_NO_ERROR; 287 } else { 288 ttype = CX_JSON_TOKEN_LITERAL; // number or literal 289 token_part_start = i; 290 } 291 } else { 292 // finish token 293 if (ctype != CX_JSON_NO_TOKEN) { 294 *result = token_create(json, false, token_part_start, i); 295 if (result->tokentype == CX_JSON_NO_TOKEN) { 296 return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE 297 } 298 if (result->tokentype == CX_JSON_TOKEN_ERROR) { 299 return CX_JSON_FORMAT_ERROR_NUMBER; 300 } 301 json->buffer.pos = i; 302 return CX_JSON_NO_ERROR; 303 } 304 } 305 } else { 306 // currently inside a string 307 if (escape_end_of_string) { 308 escape_end_of_string = false; 309 } else { 310 if (c == '""') { 311 *result = token_create(json, true, token_part_start, i + 1); 312 if (result->tokentype == CX_JSON_NO_TOKEN) { 313 return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE 314 } 315 json->buffer.pos = i + 1; 316 return CX_JSON_NO_ERROR; 317 } else if (c == '\\') { 318 escape_end_of_string = true; 319 } 320 } 321 } 322 } 323 324 if (ttype != CX_JSON_NO_TOKEN) { 325 // uncompleted token 326 size_t uncompleted_len = json->buffer.size - token_part_start; 327 if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) { 328 // current token is uncompleted 329 // save current token content 330 CxJsonToken uncompleted = { 331 ttype, true, 332 cx_strdup(cx_strn(json->buffer.space + token_part_start, uncompleted_len)) 333 }; 334 if (uncompleted.content.ptr == NULL) { 335 return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE 336 } 337 json->uncompleted = uncompleted; 338 } else { 339 // previously we also had an uncompleted token 340 // combine the uncompleted token with the current token 341 assert(json->uncompleted.allocated); 342 cxmutstr str = cx_strcat_m(json->uncompleted.content, 1, 343 cx_strn(json->buffer.space + token_part_start, uncompleted_len)); 344 if (str.ptr == NULL) { 345 return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE 346 } 347 json->uncompleted.content = str; 348 } 349 // advance the buffer position - we saved the stuff in the uncompleted token 350 json->buffer.pos += uncompleted_len; 351 } 352 353 return CX_JSON_INCOMPLETE_DATA; 354 } 355 356 // converts a Unicode codepoint to utf8 357 static unsigned codepoint_to_utf8(uint32_t codepoint, char *output_buf) { 358 if (codepoint <= 0x7F) { 359 *output_buf = (char)codepoint; 360 return 1; 361 } else if (codepoint <= 0x7FF) { 362 output_buf[0] = (char)(0xC0 | ((codepoint >> 6) & 0x1F)); 363 output_buf[1] = (char)(0x80 | (codepoint & 0x3F)); 364 return 2; 365 } else if (codepoint <= 0xFFFF) { 366 output_buf[0] = (char)(0xE0 | ((codepoint >> 12) & 0x0F)); 367 output_buf[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); 368 output_buf[2] = (char)(0x80 | (codepoint & 0x3F)); 369 return 3; 370 } else if (codepoint <= 0x10FFFF) { 371 output_buf[0] = (char)(0xF0 | ((codepoint >> 18) & 0x07)); 372 output_buf[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F)); 373 output_buf[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); 374 output_buf[3] = (char)(0x80 | (codepoint & 0x3F)); 375 return 4; 376 } 377 378 return 0; // LCOV_EXCL_LINE 379 } 380 381 // converts a utf16 surrogate pair to utf8 382 static inline uint32_t utf16pair_to_codepoint(uint16_t c0, uint16_t c1) { 383 return ((c0 - 0xD800) << 10) + (c1 - 0xDC00) + 0x10000; 384 } 385 386 static unsigned unescape_unicode_string(cxstring str, char *utf8buf) { 387 // str is supposed to start with "\uXXXX" or "\uXXXX\uXXXX" 388 // remaining bytes in the string are ignored (str may be larger!) 389 390 if (str.length < 6 || str.ptr[0] != '\\' || str.ptr[1] != 'u') { 391 return 0; 392 } 393 394 unsigned utf8len = 0; 395 cxstring ustr1 = { str.ptr + 2, 4}; 396 uint16_t utf16a, utf16b; 397 if (!cx_strtou16_lc(ustr1, &utf16a, 16, "")) { 398 uint32_t codepoint; 399 if (utf16a < 0xD800 || utf16a > 0xE000) { 400 // character is in the Basic Multilingual Plane 401 // and encoded as a single utf16 char 402 codepoint = utf16a; 403 utf8len = codepoint_to_utf8(codepoint, utf8buf); 404 } else if (utf16a >= 0xD800 && utf16a <= 0xDBFF) { 405 // character is encoded as a surrogate pair 406 // get next 6 bytes 407 if (str.length >= 12) { 408 if (str.ptr[6] == '\\' && str.ptr[7] == 'u') { 409 cxstring ustr2 = { str.ptr+8, 4 }; 410 if (!cx_strtou16_lc(ustr2, &utf16b, 16, "") 411 && utf16b >= 0xDC00 && utf16b <= 0xDFFF) { 412 codepoint = utf16pair_to_codepoint(utf16a, utf16b); 413 utf8len = codepoint_to_utf8(codepoint, utf8buf); 414 } 415 } 416 } 417 } 418 } 419 return utf8len; 420 } 421 422 static cxmutstr unescape_string(const CxAllocator *a, cxmutstr str) { 423 // note: this function expects that str contains the enclosing quotes! 424 425 cxmutstr result; 426 result.length = 0; 427 result.ptr = cxMalloc(a, str.length - 1); 428 if (result.ptr == NULL) return result; // LCOV_EXCL_LINE 429 430 bool u = false; 431 for (size_t i = 1; i < str.length - 1; i++) { 432 char c = str.ptr[i]; 433 if (u) { 434 u = false; 435 if (c == 'n') { 436 c = '\n'; 437 } else if (c == '""') { 438 c = '""'; 439 } else if (c == 't') { 440 c = '\t'; 441 } else if (c == 'r') { 442 c = '\r'; 443 } else if (c == '\\') { 444 c = '\\'; 445 } else if (c == '/') { 446 c = '/'; // always unescape, we don't need settings here 447 } else if (c == 'f') { 448 c = '\f'; 449 } else if (c == 'b') { 450 c = '\b'; 451 } else if (c == 'u') { 452 char utf8buf[4]; 453 unsigned utf8len = unescape_unicode_string( 454 cx_strn(str.ptr + i - 1, str.length + 1 - i), 455 utf8buf 456 ); 457 if(utf8len > 0) { 458 i += utf8len < 4 ? 4 : 10; 459 // add all bytes from utf8buf except the last char 460 // to the result (last char will be added below) 461 utf8len--; 462 c = utf8buf[utf8len]; 463 for (unsigned x = 0; x < utf8len; x++) { 464 result.ptr[result.length++] = utf8buf[x]; 465 } 466 } else { 467 // decoding failed, ignore the entire sequence 468 result.ptr[result.length++] = '\\'; 469 } 470 } else { 471 // TODO: discuss the behavior for unrecognized escape sequences 472 // most parsers throw an error here - we just ignore it 473 result.ptr[result.length++] = '\\'; 474 } 475 476 result.ptr[result.length++] = c; 477 } else { 478 if (c == '\\') { 479 u = true; 480 } else { 481 result.ptr[result.length++] = c; 482 } 483 } 484 } 485 result.ptr[result.length] = 0; 486 487 return result; 488 } 489 490 static cxmutstr escape_string(cxmutstr str, bool escape_slash) { 491 // note: this function produces the string without enclosing quotes 492 // the reason is that we don't want to allocate memory just for that 493 CxBuffer buf = {0}; 494 495 bool all_printable = true; 496 for (size_t i = 0; i < str.length; i++) { 497 unsigned char c = str.ptr[i]; 498 bool escape = c < 0x20 || c == '\\' || c == '""' 499 || (escape_slash && c == '/'); 500 501 if (all_printable && escape) { 502 size_t capa = str.length + 32; 503 char *space = malloc(capa); 504 if (space == NULL) return cx_mutstrn(NULL, 0); 505 cxBufferInit(&buf, space, capa, NULL, CX_BUFFER_AUTO_EXTEND); 506 cxBufferWrite(str.ptr, 1, i, &buf); 507 all_printable = false; 508 } 509 if (escape) { 510 cxBufferPut(&buf, '\\'); 511 if (c == '\"') { 512 cxBufferPut(&buf, '\"'); 513 } else if (c == '\n') { 514 cxBufferPut(&buf, 'n'); 515 } else if (c == '\t') { 516 cxBufferPut(&buf, 't'); 517 } else if (c == '\r') { 518 cxBufferPut(&buf, 'r'); 519 } else if (c == '\\') { 520 cxBufferPut(&buf, '\\'); 521 } else if (c == '/') { 522 cxBufferPut(&buf, '/'); 523 } else if (c == '\f') { 524 cxBufferPut(&buf, 'f'); 525 } else if (c == '\b') { 526 cxBufferPut(&buf, 'b'); 527 } else { 528 char code[6]; 529 snprintf(code, sizeof(code), "u%04x", (unsigned int) c); 530 cxBufferPutString(&buf, code); 531 } 532 } else if (!all_printable) { 533 cxBufferPut(&buf, c); 534 } 535 } 536 if (!all_printable) { 537 str = cx_mutstrn(buf.space, buf.size); 538 } 539 cxBufferDestroy(&buf); 540 return str; 541 } 542 543 static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) { 544 CxJsonValue *v = cxCalloc(json->allocator, 1, sizeof(CxJsonValue)); 545 if (v == NULL) return NULL; // LCOV_EXCL_LINE 546 547 // initialize the value 548 v->type = type; 549 v->allocator = json->allocator; 550 if (type == CX_JSON_ARRAY) { 551 cx_array_initialize_a(json->allocator, v->value.array.array, 16); 552 if (v->value.array.array == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE 553 } else if (type == CX_JSON_OBJECT) { 554 cx_array_initialize_a(json->allocator, v->value.object.values, 16); 555 v->value.object.indices = cxCalloc(json->allocator, 16, sizeof(size_t)); 556 if (v->value.object.values == NULL || 557 v->value.object.indices == NULL) 558 goto create_json_value_exit_error; // LCOV_EXCL_LINE 559 } 560 561 // add the new value to a possible parent 562 if (json->vbuf_size > 0) { 563 CxJsonValue *parent = json->vbuf[json->vbuf_size - 1]; 564 assert(parent != NULL); 565 if (parent->type == CX_JSON_ARRAY) { 566 CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL); 567 if (cx_array_simple_add_a(&value_realloc, parent->value.array.array, v)) { 568 goto create_json_value_exit_error; // LCOV_EXCL_LINE 569 } 570 } else if (parent->type == CX_JSON_OBJECT) { 571 // the member was already created after parsing the name 572 assert(json->uncompleted_member.name.ptr != NULL); 573 json->uncompleted_member.value = v; 574 if (json_add_objvalue(parent, json->uncompleted_member)) { 575 goto create_json_value_exit_error; // LCOV_EXCL_LINE 576 } 577 json->uncompleted_member.name = (cxmutstr) {NULL, 0}; 578 } else { 579 assert(false); // LCOV_EXCL_LINE 580 } 581 } 582 583 // add the new value to the stack, if it is an array or object 584 if (type == CX_JSON_ARRAY || type == CX_JSON_OBJECT) { 585 CxArrayReallocator vbuf_realloc = cx_array_reallocator(NULL, json->vbuf_internal); 586 if (cx_array_simple_add_a(&vbuf_realloc, json->vbuf, v)) { 587 goto create_json_value_exit_error; // LCOV_EXCL_LINE 588 } 589 } 590 591 // if currently no value is parsed, this is now the value of interest 592 if (json->parsed == NULL) { 593 json->parsed = v; 594 } 595 596 return v; 597 // LCOV_EXCL_START 598 create_json_value_exit_error: 599 cxJsonValueFree(v); 600 return NULL; 601 // LCOV_EXCL_STOP 602 } 603 604 #define JP_STATE_VALUE_BEGIN 0 605 #define JP_STATE_VALUE_END 10 606 #define JP_STATE_VALUE_BEGIN_OBJ 1 607 #define JP_STATE_OBJ_SEP_OR_CLOSE 11 608 #define JP_STATE_VALUE_BEGIN_AR 2 609 #define JP_STATE_ARRAY_SEP_OR_CLOSE 12 610 #define JP_STATE_OBJ_NAME_OR_CLOSE 5 611 #define JP_STATE_OBJ_NAME 6 612 #define JP_STATE_OBJ_COLON 7 613 614 void cxJsonInit(CxJson *json, const CxAllocator *allocator) { 615 if (allocator == NULL) { 616 allocator = cxDefaultAllocator; 617 } 618 619 memset(json, 0, sizeof(CxJson)); 620 json->allocator = allocator; 621 622 json->states = json->states_internal; 623 json->states_capacity = cx_nmemb(json->states_internal); 624 json->states[0] = JP_STATE_VALUE_BEGIN; 625 json->states_size = 1; 626 627 json->vbuf = json->vbuf_internal; 628 json->vbuf_capacity = cx_nmemb(json->vbuf_internal); 629 } 630 631 void cxJsonDestroy(CxJson *json) { 632 cxBufferDestroy(&json->buffer); 633 if (json->states != json->states_internal) { 634 free(json->states); 635 } 636 if (json->vbuf != json->vbuf_internal) { 637 free(json->vbuf); 638 } 639 cxJsonValueFree(json->parsed); 640 json->parsed = NULL; 641 if (json->uncompleted_member.name.ptr != NULL) { 642 cx_strfree_a(json->allocator, &json->uncompleted_member.name); 643 json->uncompleted_member = (CxJsonObjValue){{NULL, 0}, NULL}; 644 } 645 } 646 647 int cxJsonFilln(CxJson *json, const char *buf, size_t size) { 648 if (cxBufferEof(&json->buffer)) { 649 // reinitialize the buffer 650 cxBufferDestroy(&json->buffer); 651 cxBufferInit(&json->buffer, (char*) buf, size, 652 NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE); 653 json->buffer.size = size; 654 return 0; 655 } else { 656 return size != cxBufferAppend(buf, 1, size, &json->buffer); 657 } 658 } 659 660 static void json_add_state(CxJson *json, int state) { 661 // we have guaranteed the necessary space with cx_array_simple_reserve() 662 // therefore, we can safely add the state in the simplest way possible 663 json->states[json->states_size++] = state; 664 } 665 666 #define return_rec(code) \ 667 token_destroy(&token); \ 668 return code 669 670 static enum cx_json_status json_parse(CxJson *json) { 671 // Reserve a pointer for a possibly read value 672 CxJsonValue *vbuf = NULL; 673 674 // grab the next token 675 CxJsonToken token; 676 { 677 enum cx_json_status ret = token_parse_next(json, &token); 678 if (ret != CX_JSON_NO_ERROR) { 679 return ret; 680 } 681 } 682 683 // pop the current state 684 assert(json->states_size > 0); 685 int state = json->states[--json->states_size]; 686 687 // guarantee that at least two more states fit on the stack 688 CxArrayReallocator state_realloc = cx_array_reallocator(NULL, json->states_internal); 689 if (cx_array_simple_reserve_a(&state_realloc, json->states, 2)) { 690 return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE 691 } 692 693 694 // 0 JP_STATE_VALUE_BEGIN value begin 695 // 10 JP_STATE_VALUE_END expect value end 696 // 1 JP_STATE_VALUE_BEGIN_OBJ value begin (inside object) 697 // 11 JP_STATE_OBJ_SEP_OR_CLOSE object, expect separator, objclose 698 // 2 JP_STATE_VALUE_BEGIN_AR value begin (inside array) 699 // 12 JP_STATE_ARRAY_SEP_OR_CLOSE array, expect separator or arrayclose 700 // 5 JP_STATE_OBJ_NAME_OR_CLOSE object, expect name or objclose 701 // 6 JP_STATE_OBJ_NAME object, expect name 702 // 7 JP_STATE_OBJ_COLON object, expect ':' 703 704 if (state < 3) { 705 // push expected end state to the stack 706 json_add_state(json, 10 + state); 707 switch (token.tokentype) { 708 case CX_JSON_TOKEN_BEGIN_ARRAY: { 709 if (json_create_value(json, CX_JSON_ARRAY) == NULL) { 710 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 711 } 712 json_add_state(json, JP_STATE_VALUE_BEGIN_AR); 713 return_rec(CX_JSON_NO_ERROR); 714 } 715 case CX_JSON_TOKEN_BEGIN_OBJECT: { 716 if (json_create_value(json, CX_JSON_OBJECT) == NULL) { 717 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 718 } 719 json_add_state(json, JP_STATE_OBJ_NAME_OR_CLOSE); 720 return_rec(CX_JSON_NO_ERROR); 721 } 722 case CX_JSON_TOKEN_STRING: { 723 if ((vbuf = json_create_value(json, CX_JSON_STRING)) == NULL) { 724 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 725 } 726 cxmutstr str = unescape_string(json->allocator, token.content); 727 if (str.ptr == NULL) { 728 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 729 } 730 vbuf->value.string = str; 731 return_rec(CX_JSON_NO_ERROR); 732 } 733 case CX_JSON_TOKEN_INTEGER: 734 case CX_JSON_TOKEN_NUMBER: { 735 int type = token.tokentype == CX_JSON_TOKEN_INTEGER ? CX_JSON_INTEGER : CX_JSON_NUMBER; 736 if (NULL == (vbuf = json_create_value(json, type))) { 737 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 738 } 739 if (type == CX_JSON_INTEGER) { 740 if (cx_strtoi64(token.content, &vbuf->value.integer, 10)) { 741 return_rec(CX_JSON_FORMAT_ERROR_NUMBER); 742 } 743 } else { 744 if (cx_strtod(token.content, &vbuf->value.number)) { 745 return_rec(CX_JSON_FORMAT_ERROR_NUMBER); 746 } 747 } 748 return_rec(CX_JSON_NO_ERROR); 749 } 750 case CX_JSON_TOKEN_LITERAL: { 751 if ((vbuf = json_create_value(json, CX_JSON_LITERAL)) == NULL) { 752 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 753 } 754 if (0 == cx_strcmp(cx_strcast(token.content), cx_str("true"))) { 755 vbuf->value.literal = CX_JSON_TRUE; 756 } else if (0 == cx_strcmp(cx_strcast(token.content), cx_str("false"))) { 757 vbuf->value.literal = CX_JSON_FALSE; 758 } else { 759 vbuf->value.literal = CX_JSON_NULL; 760 } 761 return_rec(CX_JSON_NO_ERROR); 762 } 763 default: { 764 return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 765 } 766 } 767 } else if (state == JP_STATE_ARRAY_SEP_OR_CLOSE) { 768 // expect ',' or ']' 769 if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) { 770 json_add_state(json, JP_STATE_VALUE_BEGIN_AR); 771 return_rec(CX_JSON_NO_ERROR); 772 } else if (token.tokentype == CX_JSON_TOKEN_END_ARRAY) { 773 // discard the array from the value buffer 774 json->vbuf_size--; 775 return_rec(CX_JSON_NO_ERROR); 776 } else { 777 return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 778 } 779 } else if (state == JP_STATE_OBJ_NAME_OR_CLOSE || state == JP_STATE_OBJ_NAME) { 780 if (state == JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype == CX_JSON_TOKEN_END_OBJECT) { 781 // discard the obj from the value buffer 782 json->vbuf_size--; 783 return_rec(CX_JSON_NO_ERROR); 784 } else { 785 // expect string 786 if (token.tokentype != CX_JSON_TOKEN_STRING) { 787 return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 788 } 789 790 // add new entry 791 cxmutstr name = unescape_string(json->allocator, token.content); 792 if (name.ptr == NULL) { 793 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 794 } 795 assert(json->uncompleted_member.name.ptr == NULL); 796 json->uncompleted_member.name = name; 797 assert(json->vbuf_size > 0); 798 799 // next state 800 json_add_state(json, JP_STATE_OBJ_COLON); 801 return_rec(CX_JSON_NO_ERROR); 802 } 803 } else if (state == JP_STATE_OBJ_COLON) { 804 // expect ':' 805 if (token.tokentype != CX_JSON_TOKEN_NAME_SEPARATOR) { 806 return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 807 } 808 // next state 809 json_add_state(json, JP_STATE_VALUE_BEGIN_OBJ); 810 return_rec(CX_JSON_NO_ERROR); 811 } else if (state == JP_STATE_OBJ_SEP_OR_CLOSE) { 812 // expect ',' or '}' 813 if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) { 814 json_add_state(json, JP_STATE_OBJ_NAME); 815 return_rec(CX_JSON_NO_ERROR); 816 } else if (token.tokentype == CX_JSON_TOKEN_END_OBJECT) { 817 // discard the obj from the value buffer 818 json->vbuf_size--; 819 return_rec(CX_JSON_NO_ERROR); 820 } else { 821 return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 822 } 823 } else { 824 // should be unreachable 825 assert(false); 826 return_rec(-1); 827 } 828 } 829 830 CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) { 831 // check if buffer has been filled 832 if (json->buffer.space == NULL) { 833 return CX_JSON_NULL_DATA; 834 } 835 836 // initialize output value 837 *value = &cx_json_value_nothing; 838 839 // parse data 840 CxJsonStatus result; 841 do { 842 result = json_parse(json); 843 if (result == CX_JSON_NO_ERROR && json->states_size == 1) { 844 // final state reached 845 assert(json->states[0] == JP_STATE_VALUE_END); 846 assert(json->vbuf_size == 0); 847 848 // write output value 849 *value = json->parsed; 850 json->parsed = NULL; 851 852 // re-initialize state machine 853 json->states[0] = JP_STATE_VALUE_BEGIN; 854 855 return CX_JSON_NO_ERROR; 856 } 857 } while (result == CX_JSON_NO_ERROR); 858 859 // the parser might think there is no data 860 // but when we did not reach the final state, 861 // we know that there must be more to come 862 if (result == CX_JSON_NO_DATA && json->states_size > 1) { 863 return CX_JSON_INCOMPLETE_DATA; 864 } 865 866 return result; 867 } 868 869 void cxJsonValueFree(CxJsonValue *value) { 870 if (value == NULL || value->type == CX_JSON_NOTHING) return; 871 switch (value->type) { 872 case CX_JSON_OBJECT: { 873 CxJsonObject obj = value->value.object; 874 for (size_t i = 0; i < obj.values_size; i++) { 875 cxJsonValueFree(obj.values[i].value); 876 cx_strfree_a(value->allocator, &obj.values[i].name); 877 } 878 cxFree(value->allocator, obj.values); 879 cxFree(value->allocator, obj.indices); 880 break; 881 } 882 case CX_JSON_ARRAY: { 883 CxJsonArray array = value->value.array; 884 for (size_t i = 0; i < array.array_size; i++) { 885 cxJsonValueFree(array.array[i]); 886 } 887 cxFree(value->allocator, array.array); 888 break; 889 } 890 case CX_JSON_STRING: { 891 cxFree(value->allocator, value->value.string.ptr); 892 break; 893 } 894 default: { 895 break; 896 } 897 } 898 cxFree(value->allocator, value); 899 } 900 901 CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator) { 902 if (allocator == NULL) allocator = cxDefaultAllocator; 903 CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); 904 if (v == NULL) return NULL; 905 v->allocator = allocator; 906 v->type = CX_JSON_OBJECT; 907 cx_array_initialize_a(allocator, v->value.object.values, 16); 908 if (v->value.object.values == NULL) { // LCOV_EXCL_START 909 cxFree(allocator, v); 910 return NULL; 911 // LCOV_EXCL_STOP 912 } 913 v->value.object.indices = cxCalloc(allocator, 16, sizeof(size_t)); 914 if (v->value.object.indices == NULL) { // LCOV_EXCL_START 915 cxFree(allocator, v->value.object.values); 916 cxFree(allocator, v); 917 return NULL; 918 // LCOV_EXCL_STOP 919 } 920 return v; 921 } 922 923 CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator) { 924 if (allocator == NULL) allocator = cxDefaultAllocator; 925 CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); 926 if (v == NULL) return NULL; 927 v->allocator = allocator; 928 v->type = CX_JSON_ARRAY; 929 cx_array_initialize_a(allocator, v->value.array.array, 16); 930 if (v->value.array.array == NULL) { cxFree(allocator, v); return NULL; } 931 return v; 932 } 933 934 CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num) { 935 if (allocator == NULL) allocator = cxDefaultAllocator; 936 CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); 937 if (v == NULL) return NULL; 938 v->allocator = allocator; 939 v->type = CX_JSON_NUMBER; 940 v->value.number = num; 941 return v; 942 } 943 944 CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num) { 945 if (allocator == NULL) allocator = cxDefaultAllocator; 946 CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); 947 if (v == NULL) return NULL; 948 v->allocator = allocator; 949 v->type = CX_JSON_INTEGER; 950 v->value.integer = num; 951 return v; 952 } 953 954 CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char* str) { 955 return cxJsonCreateCxString(allocator, cx_str(str)); 956 } 957 958 CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str) { 959 if (allocator == NULL) allocator = cxDefaultAllocator; 960 CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); 961 if (v == NULL) return NULL; 962 v->allocator = allocator; 963 v->type = CX_JSON_STRING; 964 cxmutstr s = cx_strdup_a(allocator, str); 965 if (s.ptr == NULL) { cxFree(allocator, v); return NULL; } 966 v->value.string = s; 967 return v; 968 } 969 970 CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit) { 971 if (allocator == NULL) allocator = cxDefaultAllocator; 972 CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); 973 if (v == NULL) return NULL; 974 v->allocator = allocator; 975 v->type = CX_JSON_LITERAL; 976 v->value.literal = lit; 977 return v; 978 } 979 980 // LCOV_EXCL_START 981 // never called as long as malloc() does not return NULL 982 static void json_arr_free_temp(CxJsonValue** values, size_t count) { 983 for (size_t i = 0; i < count; i++) { 984 if (values[i] == NULL) break; 985 cxJsonValueFree(values[i]); 986 } 987 free(values); 988 } 989 // LCOV_EXCL_STOP 990 991 int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count) { 992 CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); 993 if (values == NULL) return -1; 994 for (size_t i = 0; i < count; i++) { 995 values[i] = cxJsonCreateNumber(arr->allocator, num[i]); 996 if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } 997 } 998 int ret = cxJsonArrAddValues(arr, values, count); 999 free(values); 1000 return ret; 1001 } 1002 1003 int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count) { 1004 CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); 1005 if (values == NULL) return -1; 1006 for (size_t i = 0; i < count; i++) { 1007 values[i] = cxJsonCreateInteger(arr->allocator, num[i]); 1008 if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } 1009 } 1010 int ret = cxJsonArrAddValues(arr, values, count); 1011 free(values); 1012 return ret; 1013 } 1014 1015 int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count) { 1016 CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); 1017 if (values == NULL) return -1; 1018 for (size_t i = 0; i < count; i++) { 1019 values[i] = cxJsonCreateString(arr->allocator, str[i]); 1020 if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } 1021 } 1022 int ret = cxJsonArrAddValues(arr, values, count); 1023 free(values); 1024 return ret; 1025 } 1026 1027 int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count) { 1028 CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); 1029 if (values == NULL) return -1; 1030 for (size_t i = 0; i < count; i++) { 1031 values[i] = cxJsonCreateCxString(arr->allocator, str[i]); 1032 if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } 1033 } 1034 int ret = cxJsonArrAddValues(arr, values, count); 1035 free(values); 1036 return ret; 1037 } 1038 1039 int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count) { 1040 CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); 1041 if (values == NULL) return -1; 1042 for (size_t i = 0; i < count; i++) { 1043 values[i] = cxJsonCreateLiteral(arr->allocator, lit[i]); 1044 if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } 1045 } 1046 int ret = cxJsonArrAddValues(arr, values, count); 1047 free(values); 1048 return ret; 1049 } 1050 1051 int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count) { 1052 CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator, NULL); 1053 assert(arr->type == CX_JSON_ARRAY); 1054 return cx_array_simple_copy_a(&value_realloc, 1055 arr->value.array.array, 1056 arr->value.array.array_size, 1057 val, count 1058 ); 1059 } 1060 1061 int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child) { 1062 cxmutstr k = cx_strdup_a(obj->allocator, name); 1063 if (k.ptr == NULL) return -1; 1064 CxJsonObjValue kv = {k, child}; 1065 if (json_add_objvalue(obj, kv)) { 1066 cx_strfree_a(obj->allocator, &k); 1067 return 1; 1068 } else { 1069 return 0; 1070 } 1071 } 1072 1073 CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name) { 1074 CxJsonValue* v = cxJsonCreateObj(obj->allocator); 1075 if (v == NULL) return NULL; 1076 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } 1077 return v; 1078 } 1079 1080 CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name) { 1081 CxJsonValue* v = cxJsonCreateArr(obj->allocator); 1082 if (v == NULL) return NULL; 1083 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } 1084 return v; 1085 } 1086 1087 CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num) { 1088 CxJsonValue* v = cxJsonCreateNumber(obj->allocator, num); 1089 if (v == NULL) return NULL; 1090 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } 1091 return v; 1092 } 1093 1094 CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num) { 1095 CxJsonValue* v = cxJsonCreateInteger(obj->allocator, num); 1096 if (v == NULL) return NULL; 1097 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } 1098 return v; 1099 } 1100 1101 CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str) { 1102 CxJsonValue* v = cxJsonCreateString(obj->allocator, str); 1103 if (v == NULL) return NULL; 1104 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } 1105 return v; 1106 } 1107 1108 CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str) { 1109 CxJsonValue* v = cxJsonCreateCxString(obj->allocator, str); 1110 if (v == NULL) return NULL; 1111 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } 1112 return v; 1113 } 1114 1115 CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) { 1116 CxJsonValue* v = cxJsonCreateLiteral(obj->allocator, lit); 1117 if (v == NULL) return NULL; 1118 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL;} 1119 return v; 1120 } 1121 1122 CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) { 1123 if (index >= value->value.array.array_size) { 1124 return &cx_json_value_nothing; 1125 } 1126 return value->value.array.array[index]; 1127 } 1128 1129 CxIterator cxJsonArrIter(const CxJsonValue *value) { 1130 return cxIteratorPtr( 1131 value->value.array.array, 1132 value->value.array.array_size 1133 ); 1134 } 1135 1136 CxIterator cxJsonObjIter(const CxJsonValue *value) { 1137 return cxIterator( 1138 value->value.object.values, 1139 sizeof(CxJsonObjValue), 1140 value->value.object.values_size 1141 ); 1142 } 1143 1144 CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) { 1145 CxJsonObjValue *member = json_find_objvalue(value, name); 1146 if (member == NULL) { 1147 return &cx_json_value_nothing; 1148 } else { 1149 return member->value; 1150 } 1151 } 1152 1153 CxJsonWriter cxJsonWriterCompact(void) { 1154 return (CxJsonWriter) { 1155 false, 1156 true, 1157 6, 1158 false, 1159 4, 1160 false 1161 }; 1162 } 1163 1164 CxJsonWriter cxJsonWriterPretty(bool use_spaces) { 1165 return (CxJsonWriter) { 1166 true, 1167 true, 1168 6, 1169 use_spaces, 1170 4, 1171 false 1172 }; 1173 } 1174 1175 static int cx_json_writer_indent( 1176 void *target, 1177 cx_write_func wfunc, 1178 const CxJsonWriter *settings, 1179 unsigned int depth 1180 ) { 1181 if (depth == 0) return 0; 1182 1183 // determine the width and characters to use 1184 const char* indent; // for 32 prepared chars 1185 size_t width = depth; 1186 if (settings->indent_space) { 1187 if (settings->indent == 0) return 0; 1188 width *= settings->indent; 1189 indent = " "; 1190 } else { 1191 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"; 1192 } 1193 1194 // calculate the number of write calls and write 1195 size_t full = width / 32; 1196 size_t remaining = width % 32; 1197 for (size_t i = 0; i < full; i++) { 1198 if (32 != wfunc(indent, 1, 32, target)) return 1; 1199 } 1200 if (remaining != wfunc(indent, 1, remaining, target)) return 1; 1201 1202 return 0; 1203 } 1204 1205 1206 int cx_json_write_rec( 1207 void *target, 1208 const CxJsonValue *value, 1209 cx_write_func wfunc, 1210 const CxJsonWriter *settings, 1211 unsigned int depth 1212 ) { 1213 // keep track of written items 1214 // the idea is to reduce the number of jumps for error checking 1215 size_t actual = 0, expected = 0; 1216 1217 // small buffer for number to string conversions 1218 char numbuf[40]; 1219 1220 // recursively write the values 1221 switch (value->type) { 1222 case CX_JSON_OBJECT: { 1223 const char *begin_obj = "{\n"; 1224 if (settings->pretty) { 1225 actual += wfunc(begin_obj, 1, 2, target); 1226 expected += 2; 1227 } else { 1228 actual += wfunc(begin_obj, 1, 1, target); 1229 expected++; 1230 } 1231 depth++; 1232 size_t elem_count = value->value.object.values_size; 1233 for (size_t look_idx = 0; look_idx < elem_count; look_idx++) { 1234 // get the member either via index array or directly 1235 size_t elem_idx = settings->sort_members 1236 ? look_idx 1237 : value->value.object.indices[look_idx]; 1238 CxJsonObjValue *member = &value->value.object.values[elem_idx]; 1239 if (settings->sort_members) { 1240 depth++;depth--; 1241 } 1242 1243 // possible indentation 1244 if (settings->pretty) { 1245 if (cx_json_writer_indent(target, wfunc, settings, depth)) { 1246 return 1; // LCOV_EXCL_LINE 1247 } 1248 } 1249 1250 // the name 1251 actual += wfunc("\"", 1, 1, target); 1252 cxmutstr name = escape_string(member->name, settings->escape_slash); 1253 actual += wfunc(name.ptr, 1, name.length, target); 1254 if (name.ptr != member->name.ptr) { 1255 cx_strfree(&name); 1256 } 1257 actual += wfunc("\"", 1, 1, target); 1258 const char *obj_name_sep = ": "; 1259 if (settings->pretty) { 1260 actual += wfunc(obj_name_sep, 1, 2, target); 1261 expected += 4 + member->name.length; 1262 } else { 1263 actual += wfunc(obj_name_sep, 1, 1, target); 1264 expected += 3 + member->name.length; 1265 } 1266 1267 // the value 1268 if (cx_json_write_rec(target, member->value, wfunc, settings, depth)) return 1; 1269 1270 // end of object-value 1271 if (look_idx < elem_count - 1) { 1272 const char *obj_value_sep = ",\n"; 1273 if (settings->pretty) { 1274 actual += wfunc(obj_value_sep, 1, 2, target); 1275 expected += 2; 1276 } else { 1277 actual += wfunc(obj_value_sep, 1, 1, target); 1278 expected++; 1279 } 1280 } else { 1281 if (settings->pretty) { 1282 actual += wfunc("\n", 1, 1, target); 1283 expected ++; 1284 } 1285 } 1286 } 1287 depth--; 1288 if (settings->pretty) { 1289 if (cx_json_writer_indent(target, wfunc, settings, depth)) return 1; 1290 } 1291 actual += wfunc("}", 1, 1, target); 1292 expected++; 1293 break; 1294 } 1295 case CX_JSON_ARRAY: { 1296 actual += wfunc("[", 1, 1, target); 1297 expected++; 1298 CxIterator iter = cxJsonArrIter(value); 1299 cx_foreach(CxJsonValue*, element, iter) { 1300 if (cx_json_write_rec( 1301 target, element, 1302 wfunc, settings, depth) 1303 ) return 1; 1304 1305 if (iter.index < iter.elem_count - 1) { 1306 const char *arr_value_sep = ", "; 1307 if (settings->pretty) { 1308 actual += wfunc(arr_value_sep, 1, 2, target); 1309 expected += 2; 1310 } else { 1311 actual += wfunc(arr_value_sep, 1, 1, target); 1312 expected++; 1313 } 1314 } 1315 } 1316 actual += wfunc("]", 1, 1, target); 1317 expected++; 1318 break; 1319 } 1320 case CX_JSON_STRING: { 1321 actual += wfunc("\"", 1, 1, target); 1322 cxmutstr str = escape_string(value->value.string, settings->escape_slash); 1323 actual += wfunc(str.ptr, 1, str.length, target); 1324 if (str.ptr != value->value.string.ptr) { 1325 cx_strfree(&str); 1326 } 1327 actual += wfunc("\"", 1, 1, target); 1328 expected += 2 + value->value.string.length; 1329 break; 1330 } 1331 case CX_JSON_NUMBER: { 1332 int precision = settings->frac_max_digits; 1333 // because of the way how %g is defined, we need to 1334 // double the precision and truncate ourselves 1335 precision = 1 + (precision > 15 ? 30 : 2 * precision); 1336 snprintf(numbuf, 40, "%.*g", precision, value->value.number); 1337 char *dot, *exp; 1338 unsigned char max_digits; 1339 // find the decimal separator and hope that it's one of . or , 1340 dot = strchr(numbuf, '.'); 1341 if (dot == NULL) { 1342 dot = strchr(numbuf, ','); 1343 } 1344 if (dot == NULL) { 1345 // no decimal separator found 1346 // output everything until a possible exponent 1347 max_digits = 30; 1348 dot = numbuf; 1349 } else { 1350 // found a decimal separator 1351 // output everything until the separator 1352 // and set max digits to what the settings say 1353 size_t len = dot - numbuf; 1354 actual += wfunc(numbuf, 1, len, target); 1355 expected += len; 1356 max_digits = settings->frac_max_digits; 1357 if (max_digits > 15) { 1358 max_digits = 15; 1359 } 1360 // locale independent separator 1361 if (max_digits > 0) { 1362 actual += wfunc(".", 1, 1, target); 1363 expected++; 1364 } 1365 dot++; 1366 } 1367 // find the exponent 1368 exp = strchr(dot, 'e'); 1369 if (exp == NULL) { 1370 // no exponent - output the rest 1371 if (max_digits > 0) { 1372 size_t len = strlen(dot); 1373 if (len > max_digits) { 1374 len = max_digits; 1375 } 1376 actual += wfunc(dot, 1, len, target); 1377 expected += len; 1378 } 1379 } else { 1380 // exponent found - truncate the frac digits 1381 // and then output the rest 1382 if (max_digits > 0) { 1383 size_t len = exp - dot - 1; 1384 if (len > max_digits) { 1385 len = max_digits; 1386 } 1387 actual += wfunc(dot, 1, len, target); 1388 expected += len; 1389 } 1390 actual += wfunc("e", 1, 1, target); 1391 expected++; 1392 exp++; 1393 size_t len = strlen(exp); 1394 actual += wfunc(exp, 1, len, target); 1395 expected += len; 1396 } 1397 break; 1398 } 1399 case CX_JSON_INTEGER: { 1400 snprintf(numbuf, 32, "%" PRIi64, value->value.integer); 1401 size_t len = strlen(numbuf); 1402 actual += wfunc(numbuf, 1, len, target); 1403 expected += len; 1404 break; 1405 } 1406 case CX_JSON_LITERAL: { 1407 if (value->value.literal == CX_JSON_TRUE) { 1408 actual += wfunc("true", 1, 4, target); 1409 expected += 4; 1410 } else if (value->value.literal == CX_JSON_FALSE) { 1411 actual += wfunc("false", 1, 5, target); 1412 expected += 5; 1413 } else { 1414 actual += wfunc("null", 1, 4, target); 1415 expected += 4; 1416 } 1417 break; 1418 } 1419 case CX_JSON_NOTHING: { 1420 // deliberately supported as an empty string! 1421 // users might want to just write the result 1422 // of a get operation without testing the value 1423 // and therefore this should not blow up 1424 break; 1425 } 1426 default: assert(false); // LCOV_EXCL_LINE 1427 } 1428 1429 return expected != actual; 1430 } 1431 1432 int cxJsonWrite( 1433 void *target, 1434 const CxJsonValue *value, 1435 cx_write_func wfunc, 1436 const CxJsonWriter *settings 1437 ) { 1438 assert(target != NULL); 1439 assert(value != NULL); 1440 assert(wfunc != NULL); 1441 1442 CxJsonWriter writer_default = cxJsonWriterCompact(); 1443 if (settings == NULL) { 1444 settings = &writer_default; 1445 } 1446 return cx_json_write_rec(target, value, wfunc, settings, 0); 1447 } 1448