1 /*- 2 * Copyright (c) 2010 Alistair Crooks <agc@NetBSD.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 #include <sys/types.h> 26 27 #include <inttypes.h> 28 #include <regex.h> 29 #include <stdarg.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <unistd.h> 34 35 #include "mj.h" 36 #include "defs.h" 37 38 #define JSON_ESCAPE '\xac' 39 #define JSON_INDENT 4 40 41 /* 42 * save 'n' chars of 's' in malloc'd memory 43 * 44 * optionally encode embedded quotes and null bytes 45 */ 46 static char * 47 strnsave(const char *s, int n, int encoded) 48 { 49 char *newc; 50 char *cp; 51 int i; 52 53 if (n < 0) { 54 n = (int)strlen(s); 55 } 56 NEWARRAY(char, cp, n + n + 1, "strnsave", return NULL); 57 switch (encoded) { 58 case MJ_JSON_ENCODE: 59 newc = cp; 60 for (i = 0 ; i < n ; i++) { 61 if (*s == JSON_ESCAPE) { 62 *newc++ = JSON_ESCAPE; 63 *newc++ = '1'; 64 s += 1; 65 } else if (*s == '"') { 66 *newc++ = JSON_ESCAPE; 67 *newc++ = '2'; 68 s += 1; 69 } else if (*s == 0x0) { 70 *newc++ = JSON_ESCAPE; 71 *newc++ = '0'; 72 s += 1; 73 } else { 74 *newc++ = *s++; 75 } 76 } 77 *newc = 0x0; 78 break; 79 default: 80 (void) memcpy(cp, s, (unsigned)n); 81 cp[n] = 0x0; 82 break; 83 } 84 return cp; 85 } 86 87 /* look in an object for the item */ 88 static int 89 findentry(mj_t *atom, const char *name, const unsigned from, const unsigned incr) 90 { 91 unsigned i; 92 93 for (i = from ; i < atom->c ; i += incr) { 94 if (strcmp(name, atom->value.v[i].value.s) == 0) { 95 return i; 96 } 97 } 98 return -1; 99 } 100 101 /* create a real number */ 102 static void 103 create_number(mj_t *atom, double d) 104 { 105 char number[128]; 106 107 atom->type = MJ_NUMBER; 108 atom->c = snprintf(number, sizeof(number), "%g", d); 109 atom->value.s = strnsave(number, (int)atom->c, MJ_HUMAN); 110 } 111 112 /* create an integer */ 113 static void 114 create_integer(mj_t *atom, int64_t i) 115 { 116 char number[128]; 117 118 atom->type = MJ_NUMBER; 119 atom->c = snprintf(number, sizeof(number), "%" PRIi64, i); 120 atom->value.s = strnsave(number, (int)atom->c, MJ_HUMAN); 121 } 122 123 /* create a string */ 124 static void 125 create_string(mj_t *atom, const char *s, ssize_t len) 126 { 127 atom->type = MJ_STRING; 128 atom->value.s = strnsave(s, (int)len, MJ_JSON_ENCODE); 129 atom->c = (unsigned)strlen(atom->value.s); 130 } 131 132 #define MJ_OPEN_BRACKET (MJ_OBJECT + 1) /* 8 */ 133 #define MJ_CLOSE_BRACKET (MJ_OPEN_BRACKET + 1) /* 9 */ 134 #define MJ_OPEN_BRACE (MJ_CLOSE_BRACKET + 1) /* 10 */ 135 #define MJ_CLOSE_BRACE (MJ_OPEN_BRACE + 1) /* 11 */ 136 #define MJ_COLON (MJ_CLOSE_BRACE + 1) /* 12 */ 137 #define MJ_COMMA (MJ_COLON + 1) /* 13 */ 138 139 /* return the token type, and start and finish locations in string */ 140 static int 141 gettok(const char *s, int *from, int *to, int *tok) 142 { 143 static regex_t tokregex; 144 regmatch_t matches[15]; 145 static int compiled; 146 147 if (!compiled) { 148 compiled = 1; 149 (void) regcomp(&tokregex, 150 "[ \t\r\n]*(([+-]?[0-9]{1,21}(\\.[0-9]*)?([eE][-+][0-9]+)?)|" 151 "(\"([^\"]|\\\\.)*\")|(null)|(false)|(true)|([][{}:,]))", 152 REG_EXTENDED); 153 } 154 if (regexec(&tokregex, &s[*from = *to], 15, matches, 0) != 0) { 155 return *tok = -1; 156 } 157 *to = *from + (int)(matches[1].rm_eo); 158 *tok = (matches[2].rm_so >= 0) ? MJ_NUMBER : 159 (matches[5].rm_so >= 0) ? MJ_STRING : 160 (matches[7].rm_so >= 0) ? MJ_NULL : 161 (matches[8].rm_so >= 0) ? MJ_FALSE : 162 (matches[9].rm_so >= 0) ? MJ_TRUE : 163 (matches[10].rm_so < 0) ? -1 : 164 (s[*from + (int)(matches[10].rm_so)] == '[') ? MJ_OPEN_BRACKET : 165 (s[*from + (int)(matches[10].rm_so)] == ']') ? MJ_CLOSE_BRACKET : 166 (s[*from + (int)(matches[10].rm_so)] == '{') ? MJ_OPEN_BRACE : 167 (s[*from + (int)(matches[10].rm_so)] == '}') ? MJ_CLOSE_BRACE : 168 (s[*from + (int)(matches[10].rm_so)] == ':') ? MJ_COLON : 169 MJ_COMMA; 170 *from += (int)(matches[1].rm_so); 171 return *tok; 172 } 173 174 /* minor function used to indent a JSON field */ 175 static void 176 indent(FILE *fp, unsigned depth, const char *trailer) 177 { 178 unsigned i; 179 180 for (i = 0 ; i < depth ; i++) { 181 (void) fprintf(fp, " "); 182 } 183 if (trailer) { 184 (void) fprintf(fp, "%s", trailer); 185 } 186 } 187 188 /***************************************************************************/ 189 190 /* return the number of entries in the array */ 191 int 192 mj_arraycount(mj_t *atom) 193 { 194 return atom->c; 195 } 196 197 /* create a new JSON node */ 198 int 199 mj_create(mj_t *atom, const char *type, ...) 200 { 201 va_list args; 202 ssize_t len; 203 char *s; 204 205 if (strcmp(type, "false") == 0) { 206 atom->type = MJ_FALSE; 207 atom->c = 0; 208 } else if (strcmp(type, "true") == 0) { 209 atom->type = MJ_TRUE; 210 atom->c = 1; 211 } else if (strcmp(type, "null") == 0) { 212 atom->type = MJ_NULL; 213 } else if (strcmp(type, "number") == 0) { 214 va_start(args, type); 215 create_number(atom, (double)va_arg(args, double)); 216 va_end(args); 217 } else if (strcmp(type, "integer") == 0) { 218 va_start(args, type); 219 create_integer(atom, (int64_t)va_arg(args, int64_t)); 220 va_end(args); 221 } else if (strcmp(type, "string") == 0) { 222 va_start(args, type); 223 s = (char *)va_arg(args, char *); 224 len = (size_t)va_arg(args, size_t); 225 va_end(args); 226 create_string(atom, s, len); 227 } else if (strcmp(type, "array") == 0) { 228 atom->type = MJ_ARRAY; 229 } else if (strcmp(type, "object") == 0) { 230 atom->type = MJ_OBJECT; 231 } else { 232 (void) fprintf(stderr, "weird type '%s'\n", type); 233 return 0; 234 } 235 return 1; 236 } 237 238 /* 239 * put a JSON tree into a text string 240 * 241 * optionally keep encoded quotes and null bytes 242 */ 243 int 244 mj_snprint(char *buf, size_t size, mj_t *atom, int encoded) 245 { 246 unsigned i; 247 char *s; 248 char *bp; 249 int cc; 250 251 switch(atom->type) { 252 case MJ_NULL: 253 return snprintf(buf, size, "null"); 254 case MJ_FALSE: 255 return snprintf(buf, size, "false"); 256 case MJ_TRUE: 257 return snprintf(buf, size, "true"); 258 case MJ_NUMBER: 259 return snprintf(buf, size, "%s", atom->value.s); 260 case MJ_STRING: 261 if (size < 3) 262 return 0; 263 switch (encoded) { 264 case MJ_JSON_ENCODE: 265 return snprintf(buf, size, "\"%s\"", atom->value.s); 266 default: 267 for (bp = buf, *bp++ = '"', s = atom->value.s ; 268 (size_t)(bp - buf) < size - 2 && (unsigned)(s - atom->value.s) < atom->c ; ) { 269 if (*s == JSON_ESCAPE) { 270 switch(s[1]) { 271 case '0': 272 if ((size_t)(bp - buf) < size - 3) 273 break; 274 *bp++ = '\\'; 275 *bp++ = '0'; 276 s += 2; 277 break; 278 case '1': 279 *bp++ = JSON_ESCAPE; 280 s += 2; 281 break; 282 case '2': 283 if ((size_t)(bp - buf) < size - 3) 284 break; 285 *bp++ = '\\'; 286 *bp++ = '"'; 287 s += 2; 288 break; 289 default: 290 (void) fprintf(stderr, "unrecognised character '%02x'\n", (uint8_t)s[1]); 291 s += 1; 292 break; 293 } 294 } else { 295 *bp++ = *s++; 296 } 297 } 298 *bp++ = '"'; 299 *bp = 0x0; 300 return bp - buf; 301 } 302 break; 303 case MJ_ARRAY: 304 cc = snprintf(buf, size, "[ "); 305 for (i = 0 ; i < atom->c ; i++) { 306 const char *sep = i+1 < atom->c ? ", " : " "; 307 308 cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i], encoded); 309 cc += snprintf(&buf[cc], size - cc, "%s", sep); 310 } 311 return cc + snprintf(&buf[cc], size - cc, "]\n"); 312 case MJ_OBJECT: 313 cc = snprintf(buf, size, "{ "); 314 for (i = 0 ; i < atom->c - 1; i += 2) { 315 const char *sep = i+2 < atom->c ? ", " : " "; 316 317 cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i], encoded); 318 cc += snprintf(&buf[cc], size - cc, ":"); 319 cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i + 1], encoded); 320 cc += snprintf(&buf[cc], size - cc, "%s", sep); 321 } 322 return cc + snprintf(&buf[cc], size - cc, "}\n"); 323 default: 324 (void) fprintf(stderr, "mj_snprint: weird type %d\n", atom->type); 325 return 0; 326 } 327 } 328 329 /* allocate and print the atom */ 330 int 331 mj_asprint(char **buf, mj_t *atom, int encoded) 332 { 333 size_t size; 334 335 size = mj_string_size(atom) + 1; 336 if ((*buf = calloc(1, size)) == NULL) { 337 return -1; 338 } 339 return mj_snprint(*buf, size, atom, encoded); 340 } 341 342 /* read into a JSON tree from a string */ 343 int 344 mj_parse(mj_t *atom, const char *s, int *from, int *to, int *tok) 345 { 346 int i; 347 348 switch(atom->type = *tok = gettok(s, from, to, tok)) { 349 case MJ_NUMBER: 350 atom->value.s = strnsave(&s[*from], *to - *from, MJ_HUMAN); 351 atom->c = atom->size = (unsigned)strlen(atom->value.s); 352 return gettok(s, from, to, tok); 353 case MJ_STRING: 354 atom->value.s = strnsave(&s[*from + 1], *to - *from - 2, MJ_JSON_ENCODE); 355 atom->c = atom->size = (unsigned)strlen(atom->value.s); 356 return gettok(s, from, to, tok); 357 case MJ_NULL: 358 case MJ_FALSE: 359 case MJ_TRUE: 360 atom->c = (unsigned)*to; 361 return gettok(s, from, to, tok); 362 case MJ_OPEN_BRACKET: 363 mj_create(atom, "array"); 364 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0); 365 while (mj_parse(&atom->value.v[atom->c++], s, from, to, tok) >= 0 && *tok != MJ_CLOSE_BRACKET) { 366 if (*tok != MJ_COMMA) { 367 (void) fprintf(stderr, "1. expected comma (got %d) at '%s'\n", *tok, &s[*from]); 368 break; 369 } 370 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0); 371 } 372 return gettok(s, from, to, tok); 373 case MJ_OPEN_BRACE: 374 mj_create(atom, "object"); 375 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0); 376 for (i = 0 ; mj_parse(&atom->value.v[atom->c++], s, from, to, tok) >= 0 && *tok != MJ_CLOSE_BRACE ; i++) { 377 if (((i % 2) == 0 && *tok != MJ_COLON) || ((i % 2) == 1 && *tok != MJ_COMMA)) { 378 (void) fprintf(stderr, "2. expected comma (got %d) at '%s'\n", *tok, &s[*from]); 379 break; 380 } 381 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0); 382 } 383 return gettok(s, from, to, tok); 384 default: 385 return *tok; 386 } 387 } 388 389 /* return the index of the item which corresponds to the name in the array */ 390 int 391 mj_object_find(mj_t *atom, const char *name, const unsigned from, const unsigned incr) 392 { 393 return findentry(atom, name, from, incr); 394 } 395 396 /* find an atom in a composite mj JSON node */ 397 mj_t * 398 mj_get_atom(mj_t *atom, ...) 399 { 400 unsigned i; 401 va_list args; 402 char *name; 403 int n; 404 405 switch(atom->type) { 406 case MJ_ARRAY: 407 va_start(args, atom); 408 i = va_arg(args, int); 409 va_end(args); 410 return (i < atom->c) ? &atom->value.v[i] : NULL; 411 case MJ_OBJECT: 412 va_start(args, atom); 413 name = va_arg(args, char *); 414 va_end(args); 415 return ((n = findentry(atom, name, 0, 2)) >= 0) ? &atom->value.v[n + 1] : NULL; 416 default: 417 return NULL; 418 } 419 } 420 421 /* perform a deep copy on an mj JSON atom */ 422 int 423 mj_deepcopy(mj_t *dst, mj_t *src) 424 { 425 unsigned i; 426 427 switch(src->type) { 428 case MJ_FALSE: 429 case MJ_TRUE: 430 case MJ_NULL: 431 (void) memcpy(dst, src, sizeof(*dst)); 432 return 1; 433 case MJ_STRING: 434 case MJ_NUMBER: 435 (void) memcpy(dst, src, sizeof(*dst)); 436 dst->value.s = strnsave(src->value.s, -1, MJ_HUMAN); 437 dst->c = dst->size = (unsigned)strlen(dst->value.s); 438 return 1; 439 case MJ_ARRAY: 440 case MJ_OBJECT: 441 (void) memcpy(dst, src, sizeof(*dst)); 442 NEWARRAY(mj_t, dst->value.v, dst->size, "mj_deepcopy()", return 0); 443 for (i = 0 ; i < src->c ; i++) { 444 if (!mj_deepcopy(&dst->value.v[i], &src->value.v[i])) { 445 return 0; 446 } 447 } 448 return 1; 449 default: 450 (void) fprintf(stderr, "weird type '%d'\n", src->type); 451 return 0; 452 } 453 } 454 455 /* do a deep delete on the object */ 456 void 457 mj_delete(mj_t *atom) 458 { 459 unsigned i; 460 461 switch(atom->type) { 462 case MJ_STRING: 463 case MJ_NUMBER: 464 free(atom->value.s); 465 break; 466 case MJ_ARRAY: 467 case MJ_OBJECT: 468 for (i = 0 ; i < atom->c ; i++) { 469 mj_delete(&atom->value.v[i]); 470 } 471 /* XXX - agc - causing problems? free(atom->value.v); */ 472 break; 473 default: 474 break; 475 } 476 } 477 478 /* return the string size needed for the textual output of the JSON node */ 479 int 480 mj_string_size(mj_t *atom) 481 { 482 unsigned i; 483 int cc; 484 485 switch(atom->type) { 486 case MJ_NULL: 487 case MJ_TRUE: 488 /* true */ 489 return 4; 490 case MJ_FALSE: 491 /* false */ 492 return 5; 493 case MJ_NUMBER: 494 return atom->c; 495 case MJ_STRING: 496 /* "string" */ 497 return atom->c + 2; 498 case MJ_ARRAY: 499 /* start '[ ' */ 500 for (cc = 2, i = 0 ; i < atom->c ; i++) { 501 cc += mj_string_size(&atom->value.v[i]); 502 /* separator ', ' or ' ' */ 503 cc += (i < atom->c - 1) ? 2 : 1; 504 } 505 /* end ']' */ 506 return cc + 1; 507 case MJ_OBJECT: 508 /* start '{ ' */ 509 for (cc = 2, i = 0 ; i < atom->c ; i += 2) { 510 /* key:value */ 511 cc += mj_string_size(&atom->value.v[i]) + 1 + mj_string_size(&atom->value.v[i + 1]); 512 /* separator ', ' or ' ' */ 513 cc += (i < atom->c - 1) ? 2 : 1; 514 } 515 /* end '}' */ 516 return cc + 1; 517 default: 518 (void) fprintf(stderr, "mj_string_size: weird type %d\n", atom->type); 519 return 0; 520 } 521 } 522 523 /* create a new atom, and append it to the array or object */ 524 int 525 mj_append(mj_t *atom, const char *type, ...) 526 { 527 va_list args; 528 ssize_t len; 529 char *s; 530 531 if (atom->type != MJ_ARRAY && atom->type != MJ_OBJECT) { 532 return 0; 533 } 534 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_append()", return 0); 535 va_start(args, type); 536 if (strcmp(type, "string") == 0) { 537 s = (char *)va_arg(args, char *); 538 len = (ssize_t)va_arg(args, ssize_t); 539 create_string(&atom->value.v[atom->c++], s, len); 540 } else if (strcmp(type, "integer") == 0) { 541 create_integer(&atom->value.v[atom->c++], (int64_t)va_arg(args, int64_t)); 542 } else if (strcmp(type, "object") == 0 || strcmp(type, "array") == 0) { 543 mj_deepcopy(&atom->value.v[atom->c++], (mj_t *)va_arg(args, mj_t *)); 544 } else { 545 (void) fprintf(stderr, "mj_append: weird type '%s'\n", type); 546 } 547 va_end(args); 548 return 1; 549 } 550 551 /* append a field to an object */ 552 int 553 mj_append_field(mj_t *atom, const char *name, const char *type, ...) 554 { 555 va_list args; 556 ssize_t len; 557 char *s; 558 559 if (atom->type != MJ_OBJECT) { 560 return 0; 561 } 562 mj_append(atom, "string", name, -1); 563 ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_append_field()", return 0); 564 va_start(args, type); 565 if (strcmp(type, "string") == 0) { 566 s = (char *)va_arg(args, char *); 567 len = (ssize_t)va_arg(args, ssize_t); 568 create_string(&atom->value.v[atom->c++], s, len); 569 } else if (strcmp(type, "integer") == 0) { 570 create_integer(&atom->value.v[atom->c++], (int64_t)va_arg(args, int64_t)); 571 } else if (strcmp(type, "object") == 0 || strcmp(type, "array") == 0) { 572 mj_deepcopy(&atom->value.v[atom->c++], (mj_t *)va_arg(args, mj_t *)); 573 } else { 574 (void) fprintf(stderr, "mj_append_field: weird type '%s'\n", type); 575 } 576 va_end(args); 577 return 1; 578 } 579 580 /* make sure a JSON object is politically correct */ 581 int 582 mj_lint(mj_t *obj) 583 { 584 unsigned i; 585 int ret; 586 587 switch(obj->type) { 588 case MJ_NULL: 589 case MJ_FALSE: 590 case MJ_TRUE: 591 if (obj->value.s != NULL) { 592 (void) fprintf(stderr, "null/false/true: non zero string\n"); 593 return 0; 594 } 595 return 1; 596 case MJ_NUMBER: 597 case MJ_STRING: 598 if (obj->c > obj->size) { 599 (void) fprintf(stderr, "string/number lint c (%u) > size (%u)\n", obj->c, obj->size); 600 return 0; 601 } 602 return 1; 603 case MJ_ARRAY: 604 case MJ_OBJECT: 605 if (obj->c > obj->size) { 606 (void) fprintf(stderr, "array/object lint c (%u) > size (%u)\n", obj->c, obj->size); 607 return 0; 608 } 609 for (ret = 1, i = 0 ; i < obj->c ; i++) { 610 if (!mj_lint(&obj->value.v[i])) { 611 (void) fprintf(stderr, "array/object lint found at %d of %p\n", i, obj); 612 ret = 0; 613 } 614 } 615 return ret; 616 default: 617 (void) fprintf(stderr, "problem type %d in %p\n", obj->type, obj); 618 return 0; 619 } 620 } 621 622 /* pretty-print a JSON struct - can be called recursively */ 623 int 624 mj_pretty(mj_t *mj, void *vp, unsigned depth, const char *trailer) 625 { 626 unsigned i; 627 FILE *fp; 628 char *s; 629 630 fp = (FILE *)vp; 631 switch(mj->type) { 632 case MJ_NUMBER: 633 case MJ_TRUE: 634 case MJ_FALSE: 635 case MJ_NULL: 636 indent(fp, depth, mj->value.s); 637 break; 638 case MJ_STRING: 639 indent(fp, depth, NULL); 640 mj_asprint(&s, mj, MJ_HUMAN); 641 (void) fprintf(fp, "%s", s); 642 free(s); 643 break; 644 case MJ_ARRAY: 645 indent(fp, depth, "[\n"); 646 for (i = 0 ; i < mj->c ; i++) { 647 mj_pretty(&mj->value.v[i], fp, depth + JSON_INDENT, (i < mj->c - 1) ? ",\n" : "\n"); 648 } 649 indent(fp, depth, "]"); 650 break; 651 case MJ_OBJECT: 652 indent(fp, depth, "{\n"); 653 for (i = 0 ; i < mj->c ; i += 2) { 654 mj_pretty(&mj->value.v[i], fp, depth + JSON_INDENT, " : "); 655 mj_pretty(&mj->value.v[i + 1], fp, 0, (i < mj->c - 2) ? ",\n" : "\n"); 656 } 657 indent(fp, depth, "}"); 658 break; 659 } 660 indent(fp, 0, trailer); 661 return 1; 662 } 663 664 /* show the contents of the simple atom as a string representation */ 665 const char * 666 mj_string_rep(mj_t *atom) 667 { 668 if (atom == NULL) { 669 return 0; 670 } 671 switch(atom->type) { 672 case MJ_STRING: 673 case MJ_NUMBER: 674 return atom->value.s; 675 case MJ_NULL: 676 return "null"; 677 case MJ_FALSE: 678 return "false"; 679 case MJ_TRUE: 680 return "true"; 681 default: 682 return NULL; 683 } 684 } 685