/* The MIT License (MIT) Copyright © 2020-2025 pacman64 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* You can build this command-line app by running cc -Wall -s -O3 -march=native -mtune=native -flto -o ./nj ./nj.c Building with COMPACT_OUTPUT defined makes `nj` output many fewer bytes, at the cost of using arguably worse colors. You can do that by running cc -s -O3 -march=native -mtune=native -flto -D COMPACT_OUTPUT -o ./nj ./nj.c Building for macos always uses COMPACT_OUTPUT, as the default terminal app there still doesn't support rgb colors. */ #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #endif #ifdef RED_ERRORS #define ERROR_STYLE "\x1b[38;2;204;0;0m" #ifdef __APPLE__ #define ERROR_STYLE "\x1b[31m" #endif #define ERROR_LINE(MSG) (ERROR_STYLE MSG "\x1b[0m\n") #else #define ERROR_LINE(MSG) (MSG "\n") #endif #ifdef __APPLE__ #define COMPACT_OUTPUT #endif #ifndef IBUF_SIZE #define IBUF_SIZE (32 * 1024) #endif #ifndef OBUF_SIZE #define OBUF_SIZE (8 * 1024) #endif #ifndef INDENTATION #define INDENTATION 2 #endif // CONST_SLICE initializes a slice struct using the string-constant given #define CONST_SLICE(s, x) init_slice(s, (unsigned char*)x, sizeof(x) - 1) // EMIT_CONST emits string constants without their final null byte #define EMIT_CONST(w, x) write_bytes(w, (unsigned char*)x, sizeof(x) - 1) #define RESET_STYLE "\x1b[0m" #ifdef COMPACT_OUTPUT // #define NULL_STYLE "\x1b[37m" #define NULL_STYLE "\x1b[38;5;248m" #define BOOL_STYLE "\x1b[36m" #define NUMBER_STYLE "\x1b[32m" #define NEGATIVE_STYLE "\x1b[31m" #define KEY_STYLE "\x1b[35m" // #define SYNTAX_STYLE "\x1b[37m" #define SYNTAX_STYLE "\x1b[38;5;248m" // #define NULL_STYLE "\x1b[38;5;248m" // #define BOOL_STYLE "\x1b[38;5;74m" // #define NUMBER_STYLE "\x1b[38;5;29m" // #define NEGATIVE_STYLE "\x1b[38;5;1m" // #define KEY_STYLE "\x1b[38;5;99m" // #define SYNTAX_STYLE "\x1b[38;5;248m" #else #define NULL_STYLE "\x1b[38;2;168;168;168m" #define BOOL_STYLE "\x1b[38;2;95;175;215m" #define NUMBER_STYLE "\x1b[38;2;0;135;95m" #define NEGATIVE_STYLE "\x1b[38;2;204;0;0m" #define KEY_STYLE "\x1b[38;2;135;95;255m" #define SYNTAX_STYLE "\x1b[38;2;168;168;168m" #endif const char* info = "" "nj [options...] [file...]\n" "\n" "\n" "Nice Json converts/fixes JSON/pseudo-JSON input into ANSI-styled multi-line\n" "JSON which uses 2 spaces for each indentation level.\n" "\n" "Besides styling and indenting JSON, this tool also adapts almost-JSON input\n" "into valid JSON, since it\n" "\n" " - ignores both rest-of-line and multi-line comments\n" " - ignores extra/trailing commas in arrays and objects\n" " - turns single-quoted strings/keys into double-quoted strings\n" " - double-quotes unquoted object keys\n" " - changes \\x 2-hex-digit into \\u 4-hex-digit string-escapes\n" "\n" "All options available can either start with a single or a double-dash\n" "\n" " -h show this help message\n" " -help show this help message\n" ""; typedef struct slice { unsigned char* ptr; size_t len; } slice; static inline void init_slice(slice* s, unsigned char* ptr, size_t len) { s->ptr = ptr; s->len = len; } typedef struct nj_maker { FILE* in; FILE* out; unsigned char* ibuf; size_t ilen; // how many bytes are being used in the input buffer size_t icap; // the input buffer's capacity size_t ipos; // the current position in the input buffer size_t line; // the current line, used to show useful error messages size_t pos; // the position in the current line, for error messages unsigned char* obuf; size_t ocap; // the output buffer's capacity size_t opos; // the current position in the output buffer ssize_t level; // the current indentation/nesting level int current; int next; } nj_maker; // advance_reader_pos helps func read_byte do its job static inline void advance_reader_pos(nj_maker* r, unsigned char b) { r->ipos++; if (b == '\n') { r->line++; r->pos = 1; } else { r->pos++; } } // read_byte does as it says: check its return for the value EOF, before // using it as the next byte int read_byte(nj_maker* r) { if (r->ipos < r->ilen) { // inside current chunk const unsigned char b = r->ibuf[r->ipos]; advance_reader_pos(r, b); return b; } // need to read the next block r->ipos = 0; r->ilen = fread(r->ibuf, sizeof(unsigned char), r->icap, r->in); if (r->ilen > 0) { const unsigned char b = r->ibuf[r->ipos]; advance_reader_pos(r, b); return b; } // reached the end of data return EOF; } // advance is used in most of the code, instead of calling read_byte directly static inline void advance(nj_maker* r) { r->current = r->next; r->next = read_byte(r); } void fail(nj_maker* m, int code, const char* msg); void skip_line(nj_maker* r) { while (true) { advance(r); const int lead = r->current; if (lead == EOF) { break; } if (lead == '\n') { advance(r); break; } } } void skip_multiline_comment(nj_maker* r) { unsigned char prev = 0; while (true) { advance(r); const int lead = r->current; if (lead == EOF) { break; } if (prev == '*' && lead == '/') { advance(r); break; } prev = (unsigned char)lead; } } void skip_comment(nj_maker* r) { int lead = r->current; if (lead == '#') { skip_line(r); return; } if (lead != '/') { fail(r, 1, "expected a slash to start comments"); } advance(r); lead = r->current; if (lead == '/') { skip_line(r); return; } if (lead == '*') { skip_multiline_comment(r); return; } fail(r, 1, "expected `//` or `/*` to start comments"); } static inline void seek_token(nj_maker* r) { while (true) { const int lead = r->current; if (lead != EOF && lead <= ' ') { advance(r); continue; } if (lead == '/' || lead == '#') { skip_comment(r); continue; } break; } } bool starts_with_bom(const unsigned char* b, const size_t n) { return (n >= 3 && b[0] == 0xef && b[1] == 0xbb && b[2] == 0xbf); } void restart_state(nj_maker* m, FILE* w, FILE* r) { m->in = r; m->ilen = 0; m->ipos = 0; m->out = w; m->opos = 0; m->line = 1; m->pos = 1; m->current = EOF; m->next = EOF; m->current = read_byte(m); if (m->current == EOF) { return; } m->next = read_byte(m); m->level = 0; // skip leading UTF-8 BOM (byte-order mark), if present if (starts_with_bom(m->ibuf, m->ilen)) { // a UTF-8 BOM has 3 bytes for (size_t i = 0; i < 3 && m->current != EOF; i++) { advance(m); } } } void write_byte(nj_maker* m, unsigned char b) { if (m->opos < m->ocap) { m->obuf[m->opos++] = b; return; } fwrite(m->obuf, 1, m->ocap, m->out); m->obuf[0] = b; m->opos = 1; } // write_bytes does as it says, minimizing the number of calls to fwrite void write_bytes(nj_maker* m, const unsigned char* src, size_t len) { const size_t rem = m->ocap - m->opos; if (len < rem) { memcpy(m->obuf + m->opos, src, len); m->opos += len; return; } for (size_t i = 0; i < len; i++) { write_byte(m, src[i]); } } void write_spaces(nj_maker* m, ssize_t n) { const unsigned char spaces[32] = " "; while (n > sizeof(spaces)) { write_bytes(m, spaces, sizeof(spaces)); n -= sizeof(spaces); } if (n > 0) { write_bytes(m, spaces, n); } } static inline void indent(nj_maker* m) { write_spaces(m, INDENTATION * m->level); } void flush(nj_maker* m) { if (m->opos > 0) { fwrite(m->obuf, 1, m->opos, m->out); } m->opos = 0; fflush(m->out); } // https://lemire.me/blog/2018/05/09/how-quickly-can-you-check-that-a-string-is-valid-unicode-utf-8/ static inline bool check_2_byte_rune(int a, int b) { return (0xc2 <= a && a <= 0xdf) && (0x80 <= b && b <= 0xbf); } bool check_3_byte_rune(int a, int b, int c) { return ( (a == 0xe0) && (0xa0 <= b && b <= 0xbf) && (0x80 <= c && c <= 0xbf) ) || ( (0xe1 <= a && a <= 0xec) && (0x80 <= b && b <= 0xbf) && (0x80 <= c && c <= 0xbf) ) || ( (a == 0xed) && (0x80 <= b && b <= 0x9f) && (0x80 <= c && c <= 0xbf) ) || ( (a == 0xee || a == 0xef) && (0x80 <= b && b <= 0xbf) && (0x80 <= c && c <= 0xbf) ); } bool check_4_byte_rune(int a, int b, int c, int d) { return ( (a == 0xf0) && (0x90 <= b && b <= 0xbf) && (0x80 <= c && c <= 0xbf) && (0x80 <= d && d <= 0xbf) ) || ( (a == 0xf1 || a == 0xf3) && (0x80 <= b && b <= 0xbf) && (0x80 <= c && c <= 0xbf) && (0x80 <= d && d <= 0xbf) ) || ( (a == 0xf4) && (0x80 <= b && b <= 0xbf) && (0x80 <= c && c <= 0x8f) && (0x80 <= d && d <= 0xbf) ); } // write_replacement_char is the recommended action to handle invalid bytes void write_replacement_char(nj_maker* m) { write_byte(m, 0xef); write_byte(m, 0xbf); write_byte(m, 0xbd); } void handle_invalid_rune(nj_maker* m) { // fail(m, 1, "invalid unicode value"); write_replacement_char(m); } // write_rune is following the table at https://en.wikipedia.org/wiki/UTF-8 void write_rune(nj_maker* m, uint32_t rune) { if (rune < (1 << 7)) { write_byte(m, rune); return; } if (rune < (1 << (5 + 6))) { const int a = 0b11000000 | (rune >> 6); const int b = 0b10000000 | (rune & 0b00111111); if (check_2_byte_rune(a, b)) { write_byte(m, a); write_byte(m, b); } else { write_replacement_char(m); } return; } if (rune < (1 << (4 + 6 + 6))) { const int a = 0b11100000 | (rune >> 12); const int b = 0b10000000 | ((rune >> 6) & 0b00111111); const int c = 0b10000000 | (rune & 0b00111111); if (check_3_byte_rune(a, b, c)) { write_byte(m, a); write_byte(m, b); write_byte(m, c); } else { write_replacement_char(m); } return; } if (rune < (1 << (3 + 6 + 6 + 6))) { const int a = 0b11110000 | (rune >> 18); const int b = 0b10000000 | ((rune >> 12) & 0b00111111); const int c = 0b10000000 | ((rune >> 6) & 0b00111111); const int d = 0b10000000 | (rune & 0b00111111); if (check_4_byte_rune(a, b, c, d)) { write_byte(m, a); write_byte(m, b); write_byte(m, c); write_byte(m, d); } else { write_replacement_char(m); } return; } write_replacement_char(m); } void copy_utf8_rune(nj_maker* m) { const int a = m->current; if (a == EOF) { return; } // handle 1-byte runes if (a < 128) { write_byte(m, a); return; } advance(m); const int b = m->current; if (b == EOF) { handle_invalid_rune(m); return; } // handle 2-byte runes if (check_2_byte_rune(a, b)) { write_byte(m, a); write_byte(m, b); return; } advance(m); const int c = m->current; if (c == EOF) { handle_invalid_rune(m); return; } // handle 3-byte runes if (check_3_byte_rune(a, b, c)) { write_byte(m, a); write_byte(m, b); write_byte(m, c); return; } advance(m); const int d = m->current; if (d == EOF) { handle_invalid_rune(m); return; } // handle 4-byte runes if (check_4_byte_rune(a, b, c, d)) { write_byte(m, a); write_byte(m, b); write_byte(m, c); write_byte(m, d); return; } handle_invalid_rune(m); } // debug is available to diagnose any bug found void debug(nj_maker* m, const char* fmt, ...) { va_list args; va_start(args, fmt); if (m->in != stdin) { fclose(m->in); } write_byte(m, '\n'); const unsigned long line = m->line; const unsigned long pos = m->pos; fprintf(stderr, "\x1b[46m\x1b[37mline %lu, pos %lu: ", line, pos); fprintf(stderr, fmt, args); fprintf(stderr, "\x1b[0m\n"); va_end(args); exit(10); } // fail quits this app right after showing the error message given void fail(nj_maker* m, int code, const char* msg) { const unsigned long line = m->line; const unsigned long pos = m->pos; write_byte(m, '\n'); flush(m); fprintf(stderr, ERROR_LINE("line %lu, pos %lu: %s"), line, pos, msg); exit(code); } bool demand_keyword(nj_maker* m, char* rest) { for (; rest[0] != 0; rest++) { const int lead = m->current; if (lead == EOF || lead != rest[0]) { return false; } advance(m); } return rest[0] == 0; } void handle_null(nj_maker* m) { if (!demand_keyword(m, "null")) { fail(m, 1, "expected `null` keyword"); } EMIT_CONST(m, "null"); } void handle_true(nj_maker* m) { if (!demand_keyword(m, "true")) { fail(m, 1, "expected `true` keyword"); } EMIT_CONST(m, "true"); } void handle_false(nj_maker* m) { if (!demand_keyword(m, "false")) { fail(m, 1, "expected `false` keyword"); } EMIT_CONST(m, "false"); } void handle_capital_none(nj_maker* m) { if (!demand_keyword(m, "None")) { fail(m, 1, "expected `None` keyword"); } EMIT_CONST(m, "null"); } void handle_capital_true(nj_maker* m) { if (!demand_keyword(m, "True")) { fail(m, 1, "expected `True` keyword"); } EMIT_CONST(m, "true"); } void handle_capital_false(nj_maker* m) { if (!demand_keyword(m, "False")) { fail(m, 1, "expected `False` keyword"); } EMIT_CONST(m, "false"); } void handle_digits(nj_maker* m) { if (!isdigit(m->current)) { fail(m, 1, "expected/missing digits"); } while (isdigit(m->current)) { write_byte(m, m->current); advance(m); } } void handle_number(nj_maker* m) { handle_digits(m); const int lead = m->current; if (lead == '.') { write_byte(m, '.'); advance(m); if (isdigit(m->current)) { handle_digits(m); } else { write_byte(m, '0'); } return; } if (lead == 'e' || lead == 'E') { write_byte(m, lead); advance(m); if (m->current == '+') { advance(m); } else if (m->current == '-') { write_byte(m, '-'); advance(m); } handle_digits(m); } } void handle_dot(nj_maker* m) { write_byte(m, '0'); write_byte(m, '.'); advance(m); if (!isdigit(m->current)) { fail(m, 1, "expected/missing digits after decimal dot"); } handle_digits(m); } void handle_plus_number(nj_maker* m) { advance(m); if (m->current == '.') { handle_dot(m); return; } handle_number(m); } void handle_minus_number(nj_maker* m) { write_byte(m, '-'); advance(m); if (m->current == '.') { handle_dot(m); return; } handle_number(m); } // decode_hex assumes valid hex digits, checked by func is_valid_hex uint32_t decode_hex(unsigned char hex) { if ('0' <= hex && hex <= '9') { return hex - '0'; } if ('A' <= hex && hex <= 'F') { return hex - 'A' + 10; } if ('a' <= hex && hex <= 'f') { return hex - 'a' + 10; } return 0xffff; } static inline bool is_valid_hex(unsigned char b) { return false || ('0' <= b && b <= '9') || ('A' <= b && b <= 'F') || ('a' <= b && b <= 'f'); } // handle_low_char ensures characters whose ASCII codes are lower than spaces // are properly escaped for strings void handle_low_char(nj_maker* m, int c) { const char* hex = "0123456789ABCDEF"; switch (c) { case '\t': write_byte(m, '\\'); write_byte(m, 't'); break; case '\n': write_byte(m, '\\'); write_byte(m, 'n'); break; case '\r': write_byte(m, '\\'); write_byte(m, 'r'); break; case '\b': write_byte(m, '\\'); write_byte(m, 'b'); break; case '\f': write_byte(m, '\\'); write_byte(m, 'f'); break; case '\v': write_byte(m, '\\'); write_byte(m, 'v'); break; default: write_byte(m, '\\'); write_byte(m, 'u'); write_byte(m, '0'); write_byte(m, '0'); write_byte(m, hex[c / 16]); write_byte(m, hex[c % 16]); break; } } void write_inner_string_hex_quad(nj_maker* m, const unsigned char quad[4]) { const uint32_t n = 0 + (decode_hex(quad[0]) << 12) + (decode_hex(quad[1]) << 8) + (decode_hex(quad[2]) << 4) + (decode_hex(quad[3]) << 0); switch (n) { case '"': write_byte(m, '\\'); write_byte(m, '"'); return; case '\\': write_byte(m, '\\'); write_byte(m, '\\'); return; } if (n >= ' ') { write_rune(m, n); } else { handle_low_char(m, n); } } void handle_hex_quad(nj_maker* m) { unsigned char quad[4]; for (size_t i = 0; i < 4; i++) { advance(m); const int lead = m->current; if (lead == EOF) { fail(m, 1, "end of input before end of string"); } if (is_valid_hex(lead)) { quad[i] = lead; continue; } fail(m, 1, "invalid hexadecimal digit in string"); } write_inner_string_hex_quad(m, quad); } void handle_hex_pair(nj_maker* m) { unsigned char quad[4] = {'0', '0', '0', '0'}; advance(m); const int a = m->current; advance(m); const int b = m->current; if (a == EOF || b == EOF) { fail(m, 1, "end of input before end of string"); } if (!is_valid_hex(a) || !is_valid_hex(b)) { fail(m, 1, "invalid hexadecimal digit in string"); } quad[2] = a; quad[3] = b; write_inner_string_hex_quad(m, quad); } void handle_string_escape(nj_maker* m, int c) { switch (c) { case '"': case '\\': case 'b': case 'f': case 'n': case 'r': case 't': write_byte(m, '\\'); write_byte(m, c); break; case 'u': handle_hex_quad(m); break; case 'x': handle_hex_pair(m); break; case '\'': write_byte(m, '\''); break; default: write_byte(m, m->current); break; } } ssize_t handle_inner_string(nj_maker* m) { const unsigned char quote = m->current; bool escaped = false; for (size_t i = 0; true; i++) { advance(m); int c = m->current; if (c == EOF) { fail(m, 1, "input ended before string was close-quoted"); } if (escaped) { handle_string_escape(m, c); escaped = false; continue; } switch (c) { case '\\': escaped = true; break; default: if (c == quote) { advance(m); return i; } // write_byte(m, c); if (c < ' ') { handle_low_char(m, c); } else { copy_utf8_rune(m); } break; } } } void handle_quoted_key(nj_maker* m) { if (m->current != m->next) { EMIT_CONST(m, "\"" KEY_STYLE); handle_inner_string(m); EMIT_CONST(m, SYNTAX_STYLE "\""); } else { write_byte(m, '"'); handle_inner_string(m); write_byte(m, '"'); } } void handle_string(nj_maker* m) { if (m->current != m->next) { EMIT_CONST(m, "\"" RESET_STYLE); handle_inner_string(m); EMIT_CONST(m, SYNTAX_STYLE "\""); } else { write_byte(m, '"'); handle_inner_string(m); write_byte(m, '"'); } } void handle_token(nj_maker* m, ssize_t lead_level); void handle_array(nj_maker* m) { m->level++; write_byte(m, '['); advance(m); for (size_t i = 0; true; i++) { seek_token(m); const int lead = m->current; if (lead == EOF) { fail(m, 1, "unclosed array"); } if (lead == ',') { advance(m); continue; } if (lead == ']') { m->level--; if (i > 0) { write_byte(m, '\n'); indent(m); EMIT_CONST(m, SYNTAX_STYLE "]"); } else { write_byte(m, ']'); } advance(m); return; } if (i > 0) { EMIT_CONST(m, SYNTAX_STYLE ","); } write_byte(m, '\n'); if (feof(m->out)) { return; } handle_token(m, m->level); } } void handle_unquoted_key(nj_maker* m) { EMIT_CONST(m, SYNTAX_STYLE "\"" KEY_STYLE); while (true) { int c = m->current; if (c == EOF) { fail(m, 1, "input ended with an object key"); } write_byte(m, c); advance(m); c = m->current; if (!isalpha(c) && !isdigit(c) && c != '_') { break; } } EMIT_CONST(m, SYNTAX_STYLE "\""); } void handle_object(nj_maker* m) { m->level++; write_byte(m, '{'); advance(m); for (size_t i = 0; true; i++) { seek_token(m); int lead = m->current; if (lead == EOF) { fail(m, 1, "unclosed object"); } if (lead == ',') { advance(m); continue; } if (lead == '}') { m->level--; if (i > 0) { write_byte(m, '\n'); indent(m); EMIT_CONST(m, SYNTAX_STYLE "}"); } else { write_byte(m, '}'); } advance(m); return; } if (feof(m->out)) { return; } if (lead == '"' || lead == '\'') { if (i > 0) { EMIT_CONST(m, SYNTAX_STYLE ","); } write_byte(m, '\n'); indent(m); EMIT_CONST(m, SYNTAX_STYLE); handle_quoted_key(m); } else if (isalpha(lead) || lead == '_') { if (i > 0) { EMIT_CONST(m, SYNTAX_STYLE ","); } write_byte(m, '\n'); indent(m); handle_unquoted_key(m); } else { fail(m, 1, "only strings or identifiers can be object keys"); } seek_token(m); lead = m->current; if (lead == EOF) { fail(m, 1, "input ended after object-key and before value"); } if (lead != ':') { fail(m, 1, "a `:` must follow all object keys"); } EMIT_CONST(m, ": "); advance(m); seek_token(m); if (m->current == EOF) { fail(m, 1, "input ended after a `:` following an object-key"); } handle_token(m, 0); } } // styles ties leading bytes/chars in tokens to their leading ANSI styles slice styles[256] = {}; // dispatch ties leading bytes/chars in tokens to the funcs which handle them void (*dispatch[256])() = {}; void handle_token(nj_maker* m, ssize_t lead_level) { const unsigned char b = m->current; write_spaces(m, INDENTATION * lead_level); write_bytes(m, styles[b].ptr, styles[b].len); dispatch[b](m); } // handle_invalid_token shows an error message and quits the app right after void handle_invalid_token(nj_maker* m) { char msg[64]; unsigned char c = (unsigned char)m->current; sprintf(msg, "%c (%d): invalid token", c, c); fail(m, 1, msg); } void handle_input(FILE* src) { unsigned char ibuf[IBUF_SIZE]; unsigned char obuf[OBUF_SIZE]; nj_maker m; m.ibuf = ibuf; m.icap = sizeof(ibuf); m.obuf = obuf; m.ocap = sizeof(obuf); restart_state(&m, stdout, src); // ignore leading whitespace/comment bytes, if present seek_token(&m); if (m.current == EOF) { fail(&m, 1, "empty input isn't valid JSON"); } handle_token(&m, 0); EMIT_CONST(&m, RESET_STYLE); write_byte(&m, '\n'); flush(&m); // ignore trailing whitespace/comment bytes, if present seek_token(&m); // ignore trailing semicolon, if present if (m.current == ';') { advance(&m); // ignore trailing whitespace/comment bytes, if present seek_token(&m); } if (!feof(src) || m.current != EOF) { fail(&m, 1, "unexpected trailing JSON data"); } } bool is_help_option(const char* s) { return (s[0] == '-' && s[1] != 0) && ( strcmp(s, "-h") == 0 || strcmp(s, "--h") == 0 || strcmp(s, "-help") == 0 || strcmp(s, "--help") == 0 ); } // run returns the error code int run(int nargs, char** args) { if (nargs > 0 && strcmp(args[0], "--") == 0) { nargs--; args++; } if (nargs > 1) { const char* msg = "can't use more than 1 named input"; fprintf(stderr, ERROR_LINE("%s"), msg); return 1; } // use stdin when not given a filepath if (nargs == 0 || strcmp(args[0], "") == 0 || strcmp(args[0], "-") == 0) { handle_input(stdin); return 0; } const char* path = args[0]; FILE* f = fopen(path, "rb"); if (f == NULL) { fprintf(stderr, ERROR_LINE("can't open file named '%s'"), path); return 1; } handle_input(f); fclose(f); return 0; } int main(int argc, char** argv) { #ifdef _WIN32 setmode(fileno(stdin), O_BINARY); // ensure output lines end in LF instead of CRLF on windows setmode(fileno(stdout), O_BINARY); setmode(fileno(stderr), O_BINARY); #endif if (argc > 1 && is_help_option(argv[1])) { printf("%s", info); return 0; } memset(dispatch, 0, sizeof(dispatch)); memset(styles, 0, sizeof(styles)); // the dispatch table starts as all null function-pointers for (size_t i = 0; i < sizeof(dispatch) / sizeof(dispatch[0]); i++) { dispatch[i] = handle_invalid_token; } for (size_t i = '0'; i <= '9'; i++) { dispatch[i] = handle_number; CONST_SLICE(&styles[i], NUMBER_STYLE); } dispatch['n'] = handle_null; dispatch['t'] = handle_true; dispatch['f'] = handle_false; dispatch['N'] = handle_capital_none; dispatch['T'] = handle_capital_true; dispatch['F'] = handle_capital_false; dispatch['.'] = handle_dot; dispatch['+'] = handle_plus_number; dispatch['-'] = handle_minus_number; dispatch['"'] = handle_string; dispatch['\''] = handle_string; dispatch['['] = handle_array; dispatch['{'] = handle_object; CONST_SLICE(&styles['n'], NULL_STYLE); CONST_SLICE(&styles['t'], BOOL_STYLE); CONST_SLICE(&styles['f'], BOOL_STYLE); CONST_SLICE(&styles['N'], NULL_STYLE); CONST_SLICE(&styles['T'], BOOL_STYLE); CONST_SLICE(&styles['F'], BOOL_STYLE); CONST_SLICE(&styles['.'], NUMBER_STYLE); CONST_SLICE(&styles['+'], NUMBER_STYLE); // CONST_SLICE(&styles['-'], NUMBER_STYLE); CONST_SLICE(&styles['-'], NEGATIVE_STYLE); CONST_SLICE(&styles['"'], SYNTAX_STYLE); CONST_SLICE(&styles['\''], SYNTAX_STYLE); CONST_SLICE(&styles['['], SYNTAX_STYLE); CONST_SLICE(&styles['{'], SYNTAX_STYLE); // enable full/block-buffering for standard output setvbuf(stdout, NULL, _IOFBF, 0); return run(argc - 1, argv + 1) == 0 ? 0 : 1; }