File: json0.c 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 /* 26 You can build this command-line app by running 27 28 cc -Wall -s -O2 -march=native -mtune=native -flto -o ./json0 ./json0.c 29 30 To build a unit-testing app run 31 32 cc -Wall -s -D TESTING -o ./json0_test ./json0.c 33 */ 34 35 #include <ctype.h> 36 #include <stdarg.h> 37 #include <stdbool.h> 38 #include <stdint.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 43 #ifdef _WIN32 44 #include <fcntl.h> 45 #include <windows.h> 46 #endif 47 48 #ifdef RED_ERRORS 49 #define ERROR_STYLE "\x1b[38;2;204;0;0m" 50 #ifdef __APPLE__ 51 #define ERROR_STYLE "\x1b[31m" 52 #endif 53 #define RESET_STYLE "\x1b[0m" 54 #else 55 #define ERROR_STYLE 56 #define RESET_STYLE 57 #endif 58 59 #define ERROR_LINE(MSG) (ERROR_STYLE MSG RESET_STYLE "\n") 60 61 #ifndef IBUF_SIZE 62 #define IBUF_SIZE (32 * 1024) 63 #endif 64 65 #ifndef OBUF_SIZE 66 #define OBUF_SIZE (8 * 1024) 67 #endif 68 69 const char* info = "" 70 "json0 [options...] [file...]\n" 71 "\n" 72 "\n" 73 "JSON-0 converts/fixes JSON/pseudo-JSON input into minimal JSON output.\n" 74 "Its output is always a single line, which ends with a line-feed.\n" 75 "\n" 76 "Besides minimizing bytes, this tool also adapts almost-JSON input into\n" 77 "valid JSON, since it\n" 78 "\n" 79 " - ignores both rest-of-line and multi-line comments\n" 80 " - ignores extra/trailing commas in arrays and objects\n" 81 " - turns single-quoted strings/keys into double-quoted strings\n" 82 " - double-quotes unquoted object keys\n" 83 " - changes \\x 2-hex-digit into \\u 4-hex-digit string-escapes\n" 84 "\n" 85 "All options available can either start with a single or a double-dash\n" 86 "\n" 87 " -h show this help message\n" 88 " -help show this help message\n" 89 " -jsonl emit JSON Lines, when top-level value is an array\n" 90 ""; 91 92 typedef struct j0_maker { 93 FILE* in; 94 FILE* out; 95 96 unsigned char* ibuf; 97 size_t ilen; // how many bytes are being used in the input buffer 98 size_t icap; // the input buffer's capacity 99 size_t ipos; // the current position in the input buffer 100 101 size_t line; // the current line, used to show useful error messages 102 size_t pos; // the position in the current line, for error messages 103 104 unsigned char* obuf; 105 size_t ocap; // the output buffer's capacity 106 size_t opos; // the current position in the output buffer 107 108 int current; 109 int next; 110 } j0_maker; 111 112 // advance_reader_pos helps func read_byte do its job 113 static inline void advance_reader_pos(j0_maker* r, unsigned char b) { 114 r->ipos++; 115 if (b == '\n') { 116 r->line++; 117 r->pos = 1; 118 } else { 119 r->pos++; 120 } 121 } 122 123 // read_byte does as it says: check its return for the value EOF, before 124 // using it as the next byte 125 static inline int read_byte(j0_maker* r) { 126 if (r->ipos < r->ilen) { 127 // inside current chunk 128 const unsigned char b = r->ibuf[r->ipos]; 129 advance_reader_pos(r, b); 130 return b; 131 } 132 133 // need to read the next block 134 r->ipos = 0; 135 r->ilen = fread(r->ibuf, sizeof(unsigned char), r->icap, r->in); 136 if (r->ilen > 0) { 137 const unsigned char b = r->ibuf[r->ipos]; 138 advance_reader_pos(r, b); 139 return b; 140 } 141 142 // reached the end of data 143 return EOF; 144 } 145 146 // advance is used in most of the code, instead of calling read_byte directly 147 static inline void advance(j0_maker* r) { 148 r->current = r->next; 149 r->next = read_byte(r); 150 } 151 152 void fail(j0_maker* m, int code, const char* msg); 153 154 void skip_line(j0_maker* r) { 155 while (true) { 156 advance(r); 157 const int lead = r->current; 158 159 if (lead == EOF) { 160 break; 161 } 162 163 if (lead == '\n') { 164 advance(r); 165 break; 166 } 167 } 168 } 169 170 void skip_multiline_comment(j0_maker* r) { 171 unsigned char prev = 0; 172 173 while (true) { 174 advance(r); 175 const int lead = r->current; 176 177 if (lead == EOF) { 178 break; 179 } 180 181 if (prev == '*' && lead == '/') { 182 advance(r); 183 break; 184 } 185 186 prev = (unsigned char)lead; 187 } 188 } 189 190 void skip_comment(j0_maker* r) { 191 int lead = r->current; 192 193 if (lead == '#') { 194 skip_line(r); 195 return; 196 } 197 198 if (lead != '/') { 199 fail(r, 1, "expected a slash to start comments"); 200 } 201 202 advance(r); 203 lead = r->current; 204 205 if (lead == '/') { 206 skip_line(r); 207 return; 208 } 209 210 if (lead == '*') { 211 skip_multiline_comment(r); 212 return; 213 } 214 215 fail(r, 1, "expected `//` or `/*` to start comments"); 216 } 217 218 static inline void seek_token(j0_maker* r) { 219 while (true) { 220 const int lead = r->current; 221 222 if (lead != EOF && lead <= ' ') { 223 advance(r); 224 continue; 225 } 226 227 if (lead == '/' || lead == '#') { 228 skip_comment(r); 229 continue; 230 } 231 232 break; 233 } 234 } 235 236 bool starts_with_bom(const unsigned char* b, const size_t n) { 237 return (n >= 3 && b[0] == 0xef && b[1] == 0xbb && b[2] == 0xbf); 238 } 239 240 void restart_state(j0_maker* m, FILE* w, FILE* r) { 241 m->in = r; 242 m->ilen = 0; 243 m->ipos = 0; 244 245 m->out = w; 246 m->opos = 0; 247 248 m->line = 1; 249 m->pos = 1; 250 251 m->current = EOF; 252 m->next = EOF; 253 254 m->current = read_byte(m); 255 if (m->current == EOF) { 256 return; 257 } 258 m->next = read_byte(m); 259 260 // skip leading UTF-8 BOM (byte-order mark), if present 261 if (starts_with_bom(m->ibuf, m->ilen)) { 262 // a UTF-8 BOM has 3 bytes 263 for (size_t i = 0; i < 3 && m->current != EOF; i++) { 264 advance(m); 265 } 266 } 267 } 268 269 void write_byte(j0_maker* m, unsigned char b) { 270 if (m->opos < m->ocap) { 271 m->obuf[m->opos++] = b; 272 return; 273 } 274 275 fwrite(m->obuf, 1, m->ocap, m->out); 276 m->obuf[0] = b; 277 m->opos = 1; 278 } 279 280 // write_bytes does as it says, minimizing the number of calls to fwrite 281 void write_bytes(j0_maker* m, const unsigned char* src, size_t len) { 282 const size_t rem = m->ocap - m->opos; 283 if (len < rem) { 284 memcpy(m->obuf + m->opos, src, len); 285 m->opos += len; 286 return; 287 } 288 289 for (size_t i = 0; i < len; i++) { 290 write_byte(m, src[i]); 291 } 292 } 293 294 void flush(j0_maker* m) { 295 if (m->opos > 0) { 296 fwrite(m->obuf, 1, m->opos, m->out); 297 } 298 m->opos = 0; 299 fflush(m->out); 300 } 301 302 // https://lemire.me/blog/2018/05/09/how-quickly-can-you-check-that-a-string-is-valid-unicode-utf-8/ 303 304 static inline bool check_2_byte_rune(int a, int b) { 305 return (0xc2 <= a && a <= 0xdf) && (0x80 <= b && b <= 0xbf); 306 } 307 308 bool check_3_byte_rune(int a, int b, int c) { 309 return ( 310 (a == 0xe0) && 311 (0xa0 <= b && b <= 0xbf) && 312 (0x80 <= c && c <= 0xbf) 313 ) || ( 314 (0xe1 <= a && a <= 0xec) && 315 (0x80 <= b && b <= 0xbf) && 316 (0x80 <= c && c <= 0xbf) 317 ) || ( 318 (a == 0xed) && 319 (0x80 <= b && b <= 0x9f) && 320 (0x80 <= c && c <= 0xbf) 321 ) || ( 322 (a == 0xee || a == 0xef) && 323 (0x80 <= b && b <= 0xbf) && 324 (0x80 <= c && c <= 0xbf) 325 ); 326 } 327 328 bool check_4_byte_rune(int a, int b, int c, int d) { 329 return ( 330 (a == 0xf0) && 331 (0x90 <= b && b <= 0xbf) && 332 (0x80 <= c && c <= 0xbf) && 333 (0x80 <= d && d <= 0xbf) 334 ) || ( 335 (a == 0xf1 || a == 0xf3) && 336 (0x80 <= b && b <= 0xbf) && 337 (0x80 <= c && c <= 0xbf) && 338 (0x80 <= d && d <= 0xbf) 339 ) || ( 340 (a == 0xf4) && 341 (0x80 <= b && b <= 0xbf) && 342 (0x80 <= c && c <= 0x8f) && 343 (0x80 <= d && d <= 0xbf) 344 ); 345 } 346 347 // write_replacement_char is the recommended action to handle invalid bytes 348 void write_replacement_char(j0_maker* m) { 349 write_byte(m, 0xef); 350 write_byte(m, 0xbf); 351 write_byte(m, 0xbd); 352 } 353 354 void handle_invalid_rune(j0_maker* m) { 355 // fail(m, 1, "invalid unicode value"); 356 write_replacement_char(m); 357 } 358 359 // write_rune is following the table at https://en.wikipedia.org/wiki/UTF-8 360 void write_rune(j0_maker* m, uint32_t rune) { 361 if (rune < (1 << 7)) { 362 write_byte(m, rune); 363 return; 364 } 365 366 if (rune < (1 << (5 + 6))) { 367 const int a = 0b11000000 | (rune >> 6); 368 const int b = 0b10000000 | (rune & 0b00111111); 369 if (check_2_byte_rune(a, b)) { 370 write_byte(m, a); 371 write_byte(m, b); 372 } else { 373 write_replacement_char(m); 374 } 375 return; 376 } 377 378 if (rune < (1 << (4 + 6 + 6))) { 379 const int a = 0b11100000 | (rune >> 12); 380 const int b = 0b10000000 | ((rune >> 6) & 0b00111111); 381 const int c = 0b10000000 | (rune & 0b00111111); 382 if (check_3_byte_rune(a, b, c)) { 383 write_byte(m, a); 384 write_byte(m, b); 385 write_byte(m, c); 386 } else { 387 write_replacement_char(m); 388 } 389 return; 390 } 391 392 if (rune < (1 << (3 + 6 + 6 + 6))) { 393 const int a = 0b11110000 | (rune >> 18); 394 const int b = 0b10000000 | ((rune >> 12) & 0b00111111); 395 const int c = 0b10000000 | ((rune >> 6) & 0b00111111); 396 const int d = 0b10000000 | (rune & 0b00111111); 397 if (check_4_byte_rune(a, b, c, d)) { 398 write_byte(m, a); 399 write_byte(m, b); 400 write_byte(m, c); 401 write_byte(m, d); 402 } else { 403 write_replacement_char(m); 404 } 405 return; 406 } 407 408 write_replacement_char(m); 409 } 410 411 void copy_utf8_rune(j0_maker* m) { 412 const int a = m->current; 413 414 if (a == EOF) { 415 return; 416 } 417 418 // handle 1-byte runes 419 if (a < 128) { 420 write_byte(m, a); 421 return; 422 } 423 424 advance(m); 425 const int b = m->current; 426 427 if (b == EOF) { 428 handle_invalid_rune(m); 429 return; 430 } 431 432 // handle 2-byte runes 433 if (check_2_byte_rune(a, b)) { 434 write_byte(m, a); 435 write_byte(m, b); 436 return; 437 } 438 439 advance(m); 440 const int c = m->current; 441 442 if (c == EOF) { 443 handle_invalid_rune(m); 444 return; 445 } 446 447 // handle 3-byte runes 448 if (check_3_byte_rune(a, b, c)) { 449 write_byte(m, a); 450 write_byte(m, b); 451 write_byte(m, c); 452 return; 453 } 454 455 advance(m); 456 const int d = m->current; 457 458 if (d == EOF) { 459 handle_invalid_rune(m); 460 return; 461 } 462 463 // handle 4-byte runes 464 if (check_4_byte_rune(a, b, c, d)) { 465 write_byte(m, a); 466 write_byte(m, b); 467 write_byte(m, c); 468 write_byte(m, d); 469 return; 470 } 471 472 handle_invalid_rune(m); 473 } 474 475 // debug is available to diagnose any bug found 476 void debug(j0_maker* m, const char* fmt, ...) { 477 va_list args; 478 va_start(args, fmt); 479 480 if (m->in != stdin) { 481 fclose(m->in); 482 } 483 484 write_byte(m, '\n'); 485 486 const unsigned long line = m->line; 487 const unsigned long pos = m->pos; 488 fprintf(stderr, "\x1b[46m\x1b[37mline %lu, pos %lu: ", line, pos); 489 fprintf(stderr, fmt, args); 490 fprintf(stderr, "\x1b[0m\n"); 491 492 va_end(args); 493 494 exit(10); 495 } 496 497 // fail quits this app right after showing the error message given 498 void fail(j0_maker* m, int code, const char* msg) { 499 const unsigned long line = m->line; 500 const unsigned long pos = m->pos; 501 502 write_byte(m, '\n'); 503 flush(m); 504 fprintf(stderr, ERROR_LINE("line %lu, pos %lu: %s"), line, pos, msg); 505 exit(code); 506 } 507 508 bool demand_keyword(j0_maker* m, char* rest) { 509 for (; rest[0] != 0; rest++) { 510 const int lead = m->current; 511 if (lead == EOF || lead != rest[0]) { 512 return false; 513 } 514 advance(m); 515 } 516 517 return rest[0] == 0; 518 } 519 520 void handle_null(j0_maker* m) { 521 if (!demand_keyword(m, "null")) { 522 fail(m, 1, "expected `null` keyword"); 523 } 524 write_bytes(m, (unsigned char*)"null", 4); 525 } 526 527 void handle_true(j0_maker* m) { 528 if (!demand_keyword(m, "true")) { 529 fail(m, 1, "expected `true` keyword"); 530 } 531 write_bytes(m, (unsigned char*)"true", 4); 532 } 533 534 void handle_false(j0_maker* m) { 535 if (!demand_keyword(m, "false")) { 536 fail(m, 1, "expected `false` keyword"); 537 } 538 write_bytes(m, (unsigned char*)"false", 5); 539 } 540 541 void handle_capital_none(j0_maker* m) { 542 if (!demand_keyword(m, "None")) { 543 fail(m, 1, "expected `None` keyword"); 544 } 545 write_bytes(m, (unsigned char*)"null", 4); 546 } 547 548 void handle_capital_true(j0_maker* m) { 549 if (!demand_keyword(m, "True")) { 550 fail(m, 1, "expected `True` keyword"); 551 } 552 write_bytes(m, (unsigned char*)"true", 4); 553 } 554 555 void handle_capital_false(j0_maker* m) { 556 if (!demand_keyword(m, "False")) { 557 fail(m, 1, "expected `False` keyword"); 558 } 559 write_bytes(m, (unsigned char*)"false", 5); 560 } 561 562 void handle_digits(j0_maker* m) { 563 if (!isdigit(m->current)) { 564 fail(m, 1, "expected/missing digits"); 565 } 566 567 while (isdigit(m->current)) { 568 write_byte(m, m->current); 569 advance(m); 570 } 571 } 572 573 void handle_number(j0_maker* m) { 574 handle_digits(m); 575 576 const int lead = m->current; 577 578 if (lead == 'n') { 579 advance(m); 580 return; 581 } 582 583 if (lead == '.') { 584 write_byte(m, '.'); 585 advance(m); 586 587 if (isdigit(m->current)) { 588 handle_digits(m); 589 } else { 590 write_byte(m, '0'); 591 } 592 return; 593 } 594 595 if (lead == 'e' || lead == 'E') { 596 write_byte(m, lead); 597 advance(m); 598 599 if (m->current == '+') { 600 advance(m); 601 } else if (m->current == '-') { 602 write_byte(m, '-'); 603 advance(m); 604 } 605 606 handle_digits(m); 607 } 608 } 609 610 void handle_dot(j0_maker* m) { 611 write_byte(m, '0'); 612 write_byte(m, '.'); 613 advance(m); 614 615 if (!isdigit(m->current)) { 616 fail(m, 1, "expected/missing digits after decimal dot"); 617 } 618 handle_digits(m); 619 } 620 621 void handle_plus_number(j0_maker* m) { 622 advance(m); 623 624 if (m->current == '.') { 625 handle_dot(m); 626 return; 627 } 628 handle_number(m); 629 } 630 631 void handle_minus_number(j0_maker* m) { 632 write_byte(m, '-'); 633 advance(m); 634 635 if (m->current == '.') { 636 handle_dot(m); 637 return; 638 } 639 handle_number(m); 640 } 641 642 // decode_hex assumes valid hex digits, checked by func is_valid_hex 643 uint32_t decode_hex(unsigned char hex) { 644 if ('0' <= hex && hex <= '9') { 645 return hex - '0'; 646 } 647 if ('A' <= hex && hex <= 'F') { 648 return hex - 'A' + 10; 649 } 650 if ('a' <= hex && hex <= 'f') { 651 return hex - 'a' + 10; 652 } 653 return 0xffff; 654 } 655 656 static inline bool is_valid_hex(unsigned char b) { 657 return false || 658 ('0' <= b && b <= '9') || 659 ('A' <= b && b <= 'F') || 660 ('a' <= b && b <= 'f'); 661 } 662 663 // handle_low_char ensures characters whose ASCII codes are lower than spaces 664 // are properly escaped for strings 665 void handle_low_char(j0_maker* m, int c) { 666 const char* hex = "0123456789ABCDEF"; 667 668 switch (c) { 669 case '\t': 670 write_byte(m, '\\'); 671 write_byte(m, 't'); 672 break; 673 case '\n': 674 write_byte(m, '\\'); 675 write_byte(m, 'n'); 676 break; 677 case '\r': 678 write_byte(m, '\\'); 679 write_byte(m, 'r'); 680 break; 681 case '\b': 682 write_byte(m, '\\'); 683 write_byte(m, 'b'); 684 break; 685 case '\f': 686 write_byte(m, '\\'); 687 write_byte(m, 'f'); 688 break; 689 case '\v': 690 write_byte(m, '\\'); 691 write_byte(m, 'v'); 692 break; 693 default: 694 write_byte(m, '\\'); 695 write_byte(m, 'u'); 696 write_byte(m, '0'); 697 write_byte(m, '0'); 698 write_byte(m, hex[c / 16]); 699 write_byte(m, hex[c % 16]); 700 break; 701 } 702 } 703 704 void write_inner_string_hex_quad(j0_maker* m, const unsigned char quad[4]) { 705 const uint32_t n = 0 + 706 (decode_hex(quad[0]) << 12) + 707 (decode_hex(quad[1]) << 8) + 708 (decode_hex(quad[2]) << 4) + 709 (decode_hex(quad[3]) << 0); 710 711 switch (n) { 712 case '"': 713 write_byte(m, '\\'); 714 write_byte(m, '"'); 715 return; 716 case '\\': 717 write_byte(m, '\\'); 718 write_byte(m, '\\'); 719 return; 720 } 721 722 if (n >= ' ') { 723 write_rune(m, n); 724 } else { 725 handle_low_char(m, n); 726 } 727 } 728 729 void handle_hex_quad(j0_maker* m) { 730 unsigned char quad[4]; 731 for (size_t i = 0; i < 4; i++) { 732 advance(m); 733 const int lead = m->current; 734 if (lead == EOF) { 735 fail(m, 1, "end of input before end of string"); 736 } 737 if (is_valid_hex(lead)) { 738 quad[i] = lead; 739 continue; 740 } 741 fail(m, 1, "invalid hexadecimal digit in string"); 742 } 743 744 write_inner_string_hex_quad(m, quad); 745 } 746 747 void handle_hex_pair(j0_maker* m) { 748 unsigned char quad[4] = {'0', '0', '0', '0'}; 749 advance(m); 750 const int a = m->current; 751 advance(m); 752 const int b = m->current; 753 if (a == EOF || b == EOF) { 754 fail(m, 1, "end of input before end of string"); 755 } 756 if (!is_valid_hex(a) || !is_valid_hex(b)) { 757 fail(m, 1, "invalid hexadecimal digit in string"); 758 } 759 760 quad[2] = a; 761 quad[3] = b; 762 write_inner_string_hex_quad(m, quad); 763 } 764 765 void handle_string_escape(j0_maker* m, int c) { 766 switch (c) { 767 case '"': 768 case '\\': 769 case 'b': 770 case 'f': 771 case 'n': 772 case 'r': 773 case 't': 774 write_byte(m, '\\'); 775 write_byte(m, c); 776 break; 777 case 'u': 778 handle_hex_quad(m); 779 break; 780 case 'x': 781 handle_hex_pair(m); 782 break; 783 case '\'': 784 write_byte(m, '\''); 785 break; 786 default: 787 write_byte(m, m->current); 788 break; 789 } 790 } 791 792 void handle_string(j0_maker* m) { 793 const unsigned char quote = m->current; 794 bool escaped = false; 795 796 write_byte(m, '"'); 797 798 while (true) { 799 advance(m); 800 801 int c = m->current; 802 if (c == EOF) { 803 fail(m, 1, "input ended before string was close-quoted"); 804 } 805 806 if (escaped) { 807 handle_string_escape(m, c); 808 escaped = false; 809 continue; 810 } 811 812 switch (c) { 813 case '\\': 814 escaped = true; 815 break; 816 default: 817 if (c == quote) { 818 write_byte(m, '"'); 819 advance(m); 820 return; 821 } 822 823 // write_byte(m, c); 824 if (c < ' ') { 825 handle_low_char(m, c); 826 } else { 827 copy_utf8_rune(m); 828 } 829 break; 830 } 831 } 832 } 833 834 void handle_token(j0_maker* m); 835 836 void handle_array(j0_maker* m) { 837 size_t items = 0; 838 const unsigned char end = m->current == '[' ? ']' : ')'; 839 write_byte(m, '['); 840 advance(m); 841 842 while (true) { 843 seek_token(m); 844 const int lead = m->current; 845 846 if (lead == EOF) { 847 fail(m, 1, "unclosed array"); 848 } 849 850 if (lead == ',') { 851 advance(m); 852 continue; 853 } 854 855 if (lead == end) { 856 write_byte(m, ']'); 857 advance(m); 858 return; 859 } 860 861 if (items > 0) { 862 write_byte(m, ','); 863 } 864 if (feof(m->out)) { 865 return; 866 } 867 handle_token(m); 868 items++; 869 } 870 } 871 872 // handle_array_jsonl is a slight variation of func handle_array: this one is 873 // used to handle top-level arrays when running in JSON Lines mode, to emit 874 // line-feeds after each item, instead of commas between them 875 void handle_array_jsonl(j0_maker* m) { 876 const unsigned char end = m->current == '[' ? ']' : ')'; 877 advance(m); 878 879 while (true) { 880 seek_token(m); 881 const int lead = m->current; 882 883 if (lead == EOF) { 884 fail(m, 1, "unclosed array"); 885 } 886 887 if (lead == ',') { 888 advance(m); 889 continue; 890 } 891 892 if (lead == end) { 893 advance(m); 894 return; 895 } 896 897 if (feof(m->out)) { 898 return; 899 } 900 901 handle_token(m); 902 write_byte(m, '\n'); 903 } 904 } 905 906 void handle_unquoted_key(j0_maker* m) { 907 write_byte(m, '"'); 908 909 while (true) { 910 int c = m->current; 911 if (c == EOF) { 912 fail(m, 1, "input ended with an object key"); 913 } 914 915 write_byte(m, c); 916 advance(m); 917 918 c = m->current; 919 if (!isalpha(c) && !isdigit(c) && c != '_') { 920 break; 921 } 922 } 923 924 write_byte(m, '"'); 925 } 926 927 void handle_object(j0_maker* m) { 928 size_t items = 0; 929 write_byte(m, '{'); 930 advance(m); 931 932 while (true) { 933 seek_token(m); 934 int lead = m->current; 935 936 if (lead == EOF) { 937 fail(m, 1, "unclosed object"); 938 } 939 940 if (lead == ',') { 941 advance(m); 942 continue; 943 } 944 945 if (lead == '}') { 946 write_byte(m, '}'); 947 advance(m); 948 return; 949 } 950 951 if (feof(m->out)) { 952 return; 953 } 954 955 if (lead == '"' || lead == '\'') { 956 if (items > 0) { 957 write_byte(m, ','); 958 } 959 handle_string(m); 960 } else if (isalpha(lead) || lead == '_') { 961 if (items > 0) { 962 write_byte(m, ','); 963 } 964 handle_unquoted_key(m); 965 } else { 966 fail(m, 1, "only strings or identifiers can be object keys"); 967 } 968 969 seek_token(m); 970 lead = m->current; 971 972 if (lead == EOF) { 973 fail(m, 1, "input ended after object-key and before value"); 974 } 975 976 if (lead != ':') { 977 fail(m, 1, "a `:` must follow all object keys"); 978 } 979 980 write_byte(m, ':'); 981 advance(m); 982 983 seek_token(m); 984 if (m->current == EOF) { 985 fail(m, 1, "input ended after a `:` following an object-key"); 986 } 987 988 handle_token(m); 989 items++; 990 } 991 } 992 993 // dispatch ties leading bytes/chars in tokens to the funcs which handle them 994 void (*dispatch[256])() = { 995 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 996 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 997 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 998 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 999 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1000 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1001 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1002 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1003 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1004 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1005 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1006 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1007 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1008 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1009 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1010 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1011 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1012 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1013 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1014 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1015 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1016 NULL, NULL, NULL, NULL, 1017 }; 1018 1019 void handle_token(j0_maker* m) { 1020 dispatch[m->current](m); 1021 } 1022 1023 // handle_invalid_token shows an error message and quits the app right after 1024 void handle_invalid_token(j0_maker* m) { 1025 char msg[64]; 1026 unsigned char c = (unsigned char)m->current; 1027 sprintf(msg, "%c (%d): invalid token", c, c); 1028 fail(m, 1, msg); 1029 } 1030 1031 void handle_array_jsonl(j0_maker* m); 1032 1033 void handle_input(FILE* out, FILE* src, bool jsonl) { 1034 unsigned char ibuf[IBUF_SIZE]; 1035 unsigned char obuf[OBUF_SIZE]; 1036 1037 j0_maker m; 1038 m.ibuf = ibuf; 1039 m.icap = sizeof(ibuf); 1040 m.obuf = obuf; 1041 m.ocap = sizeof(obuf); 1042 restart_state(&m, out, src); 1043 1044 // ignore leading whitespace/comment bytes, if present 1045 seek_token(&m); 1046 1047 if (m.current == EOF) { 1048 fail(&m, 1, "empty input isn't valid JSON"); 1049 } 1050 1051 if (jsonl && m.current == '[') { 1052 handle_array_jsonl(&m); 1053 } else { 1054 handle_token(&m); 1055 write_byte(&m, '\n'); 1056 } 1057 flush(&m); 1058 1059 // ignore trailing whitespace/comment bytes, if present 1060 seek_token(&m); 1061 1062 // ignore trailing semicolon, if present 1063 if (m.current == ';') { 1064 advance(&m); 1065 // ignore trailing whitespace/comment bytes, if present 1066 seek_token(&m); 1067 } 1068 1069 if (!feof(src) || m.current != EOF) { 1070 fail(&m, 1, "unexpected trailing JSON data"); 1071 } 1072 } 1073 1074 bool is_help_option(const char* s) { 1075 return (s[0] == '-' && s[1] != 0) && ( 1076 strcmp(s, "-h") == 0 || 1077 strcmp(s, "--h") == 0 || 1078 strcmp(s, "-help") == 0 || 1079 strcmp(s, "--help") == 0 1080 ); 1081 } 1082 1083 bool is_jsonl_option(const char* s) { 1084 return (s[0] == '-' && s[1] != 0) && ( 1085 strcmp(s, "-jl") == 0 || 1086 strcmp(s, "--jl") == 0 || 1087 strcmp(s, "-jsonl") == 0 || 1088 strcmp(s, "--jsonl") == 0 1089 ); 1090 } 1091 1092 // run returns the error code 1093 int run(int nargs, char** args) { 1094 bool jsonl = false; 1095 if (nargs > 0 && is_jsonl_option(args[0])) { 1096 jsonl = true; 1097 nargs--; 1098 args++; 1099 } 1100 1101 if (nargs > 0 && strcmp(args[0], "--") == 0) { 1102 nargs--; 1103 args++; 1104 } 1105 1106 if (nargs > 1) { 1107 const char* msg = "can't use more than 1 named input"; 1108 fprintf(stderr, ERROR_LINE("%s"), msg); 1109 return 1; 1110 } 1111 1112 // use stdin when not given a filepath 1113 if (nargs == 0 || strcmp(args[0], "") == 0 || strcmp(args[0], "-") == 0) { 1114 handle_input(stdout, stdin, jsonl); 1115 return 0; 1116 } 1117 1118 const char* path = args[0]; 1119 FILE* f = fopen(path, "rb"); 1120 if (f == NULL) { 1121 fprintf(stderr, ERROR_LINE("can't open file named '%s'"), path); 1122 return 1; 1123 } 1124 1125 handle_input(stdout, f, jsonl); 1126 fclose(f); 1127 1128 return 0; 1129 } 1130 1131 #ifdef TESTING 1132 bool run_test(const char* name, const char* input, const char* expected) { 1133 unsigned char result[OBUF_SIZE]; 1134 1135 fprintf(stdout, "running test named \"%s\"\n", name); 1136 1137 FILE* in = fmemopen((void*)input, strlen(input), "rb"); 1138 if (in == NULL) { 1139 fprintf(stdout, "fmemopen failed\n"); 1140 return false; 1141 } 1142 1143 memset(result, 0, sizeof(result)); 1144 FILE* out = fmemopen((void*)result, sizeof(result), "wb"); 1145 if (out == NULL) { 1146 fprintf(stdout, "fmemopen failed\n"); 1147 return false; 1148 } 1149 1150 handle_input(out, in, false); 1151 1152 fclose(out); 1153 fclose(in); 1154 1155 // remove trailing line-feed from the result 1156 for (ssize_t i = sizeof(result) - 1; i >= 0; i--) { 1157 if (result[i] == '\n') { 1158 result[i] = 0; 1159 break; 1160 } 1161 } 1162 1163 const bool ok = strcmp((char*)result, expected) == 0; 1164 if (!ok) { 1165 fprintf(stdout, " input: %s\n", input); 1166 fprintf(stdout, " expected: %s\n", expected); 1167 fprintf(stdout, " result: %s\n", result); 1168 } 1169 return ok; 1170 } 1171 1172 int test() { 1173 typedef struct test_case { 1174 const char* name; 1175 const char* input; 1176 const char* expected; 1177 } test_case; 1178 1179 test_case cases[] = { 1180 {"null", "null", "null"}, 1181 {"false", "false", "false"}, 1182 {"true", "true", "true"}, 1183 {"None", "None", "null"}, 1184 {"False", "False", "false"}, 1185 {"True", "True", "true"}, 1186 {"zero", "0", "0"}, 1187 {"zero with decimals", "0.0000", "0.0000"}, 1188 {"negative number", "-1230.324", "-1230.324"}, 1189 {"leading plus", "+1230.324", "1230.324"}, 1190 {"leading dot", ".123", "0.123"}, 1191 {"leading negative dot", "-.123", "-0.123"}, 1192 {"leading positive dot", "+.123", "0.123"}, 1193 {"empty string", "\"\"", "\"\""}, 1194 {"single-quoted string", "'abc def'", "\"abc def\""}, 1195 { 1196 "string with double-quotes in it", 1197 "\"\\\"cats and dogs\\\" goes the saying\"", 1198 "\"\\\"cats and dogs\\\" goes the saying\"", 1199 }, 1200 { 1201 "string with escaped hex-digit values in it", 1202 "\"\\x00\\u0000\\x09\"", 1203 "\"\\u0000\\u0000\\t\"", 1204 }, 1205 {"empty array", "[]", "[]"}, 1206 {"empty array, extra comma", "[ , ]", "[]"}, 1207 {"empty object", "{}", "{}"}, 1208 {"empty object, extra commas", "{,, , ,,}", "{}"}, 1209 {"numeric array", "[,,1, 2, 3, ]", "[1,2,3]"}, 1210 {"simple nested array", "[1, 2, 3, []]", "[1,2,3,[]]"}, 1211 { 1212 "another simple nested array", 1213 "[1, 2, 3, [false,\"abc\"]]", 1214 "[1,2,3,[false,\"abc\"]]", 1215 }, 1216 { 1217 "fancier nested array", 1218 "[1, 2, 3, [[ -.233, false,] , , ,,, 'abc']]", 1219 "[1,2,3,[[-0.233,false],\"abc\"]]", 1220 }, 1221 { 1222 "simple object, extra commas", 1223 "{,'abc' : 123, , ,'def': 987,}", 1224 "{\"abc\":123,\"def\":987}", 1225 }, 1226 { 1227 "simple object, extra commas, unquoted object keys", 1228 "{,abc : 123, , ,def: 987,}", 1229 "{\"abc\":123,\"def\":987}", 1230 }, 1231 { 1232 "numeric array with trailing single-line comment", 1233 "[1, 2, 3, ] // comments aren't valid JSON", 1234 "[1,2,3]", 1235 }, 1236 { 1237 "numeric array with comments", 1238 "/* hi there */ [1, 2, /* 3 better be next */ 3, ]" 1239 " // I'll have the last word # you wish", 1240 "[1,2,3]", 1241 }, 1242 { 1243 "self-compacting shebang", 1244 "#!/usr/bin/json0\n[, 1 , , 2 , , , 3]", 1245 "[1,2,3]", 1246 }, 1247 { 1248 "pyon example 1", 1249 "[True,False,'abc\\x0adef',None,+12.45]", 1250 "[true,false,\"abc\\ndef\",null,12.45]", 1251 }, 1252 { 1253 "pyon example 2", 1254 "[{'abc':123},'abc\\x0adef',None,+12.45]", 1255 "[{\"abc\":123},\"abc\\ndef\",null,12.45]", 1256 }, 1257 }; 1258 1259 size_t errors = 0; 1260 for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { 1261 const char* s = cases[i].name; 1262 if (!run_test(s, cases[i].input, cases[i].expected)) { 1263 fprintf(stdout, "\x1b[31mtest named \"%s\" failed\x1b[0m\n", s); 1264 errors++; 1265 } 1266 } 1267 return errors == 0 ? 0 : 1; 1268 } 1269 #endif 1270 1271 int main(int argc, char** argv) { 1272 #ifdef _WIN32 1273 setmode(fileno(stdin), O_BINARY); 1274 // ensure output lines end in LF instead of CRLF on windows 1275 setmode(fileno(stdout), O_BINARY); 1276 setmode(fileno(stderr), O_BINARY); 1277 #endif 1278 1279 #ifndef TESTING 1280 if (argc > 1 && is_help_option(argv[1])) { 1281 printf("%s", info); 1282 return 0; 1283 } 1284 #endif 1285 1286 // the dispatch table starts as all null function-pointers 1287 for (size_t i = 0; i < sizeof(dispatch) / sizeof(dispatch[0]); i++) { 1288 dispatch[i] = handle_invalid_token; 1289 } 1290 1291 for (size_t i = '0'; i <= '9'; i++) { 1292 dispatch[i] = handle_number; 1293 } 1294 1295 dispatch['n'] = handle_null; 1296 dispatch['t'] = handle_true; 1297 dispatch['f'] = handle_false; 1298 dispatch['N'] = handle_capital_none; 1299 dispatch['T'] = handle_capital_true; 1300 dispatch['F'] = handle_capital_false; 1301 dispatch['.'] = handle_dot; 1302 dispatch['+'] = handle_plus_number; 1303 dispatch['-'] = handle_minus_number; 1304 dispatch['"'] = handle_string; 1305 dispatch['\''] = handle_string; 1306 dispatch['['] = handle_array; 1307 dispatch['('] = handle_array; 1308 dispatch['{'] = handle_object; 1309 1310 #ifdef TESTING 1311 return test(); 1312 #else 1313 // enable full/block-buffering for standard output 1314 setvbuf(stdout, NULL, _IOFBF, 0); 1315 1316 return run(argc - 1, argv + 1) == 0 ? 0 : 1; 1317 #endif 1318 }