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 }