├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── assets └── screenshot1.png ├── json_gen.py └── src ├── buffer.c ├── buffer.h ├── json.c ├── json.h ├── main.c ├── parse.c ├── parse.h ├── print.c ├── print.h ├── stack.h ├── term.h ├── test.c ├── theme.h ├── trace.h └── util.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | trace.txt -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Simeon Krastnikov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -std=c99 -Wall -O3 3 | OBJFILES = src/buffer.o src/json.o src/parse.o src/print.o 4 | 5 | jinsp: src/main.o $(OBJFILES) 6 | $(CC) $(CFLAGS) $^ -o $@ 7 | 8 | test: src/test.o $(OBJFILES) 9 | $(CC) $(CFLAGS) $^ -o $@ 10 | 11 | src/%.o: src/%.c 12 | $(CC) $(CFLAGS) -c $< -o $@ 13 | 14 | clean: 15 | rm src/*.o 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jinsp 2 | 3 | ![screenshot](assets/screenshot1.png) 4 | 5 | jinsp is a Linux terminal-based interface (TUI) for browsing and inspecting JSON data. 6 | It allows navigation through a JSON file's tree structure using [Miller columns](https://en.wikipedia.org/wiki/Miller_columns) (in a manner similar, for instance, to that of the [ranger file manager](https://github.com/ranger/ranger)). 7 | 8 | ## Build 9 | 10 | Run `make` in the root directory. 11 | 12 | ## Usage 13 | 14 | Launch the interface by running `./jinsp `. 15 | 16 | The rightmost pane shows a flattened (read-only) preview of the currently selected element rooted at the position shown on the top line (initially the root element). 17 | 18 | Navigation trough the JSON tree structure can be performed using the following keyboard keys: 19 | 20 | * Use the **Up**/**Down** arrow keys (as well as **PgUp**/**PgDown** and **Home**/**End**) to traverse through the siblings of the currently selected element 21 | * Use the **Left** arrow key to go back to its parent 22 | * Use the **Right** arrow key (or **Return**/**Enter**) to start traversing its children 23 | 24 | Any combination of these operations can also be performed using the mouse, by clicking on any given element (within all panes except the rightmost). Note that clicking on the currently selected element will alternate between previewing and expanding it (navigating to one of its children). 25 | 26 | Other keyboard controls: 27 | * **q** or **Esc**: quit 28 | * **/** followed by a keyword and **Return**/**Enter**: case-sensitive search starting from current position (**Esc** to abort input) 29 | * **n**/**N**: navigate search results forwards and backwards, respectively 30 | 31 | -------------------------------------------------------------------------------- /assets/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simeonkr/jinsp/e0cefb24a587cbe64e5da57be914bd3b98d3e57a/assets/screenshot1.png -------------------------------------------------------------------------------- /json_gen.py: -------------------------------------------------------------------------------- 1 | # generate a JSON file for testing purposes 2 | 3 | import sys 4 | import json 5 | import string 6 | import random 7 | import numpy as np 8 | 9 | SCALE = 512 10 | 11 | def gen_string(_): 12 | size = np.random.geometric(0.01) 13 | return ''.join([ 14 | random.choice(string.ascii_lowercase + string.digits) 15 | for _ in range(size) 16 | ]) 17 | 18 | def gen_number(_): 19 | return random.randint(1, 2**20) 20 | 21 | def gen_key(_): 22 | size = np.random.geometric(0.1) 23 | return ''.join([ 24 | random.choice(string.ascii_lowercase + string.digits) 25 | for _ in range(size) 26 | ]) 27 | 28 | def gen_value(depth): 29 | typ = random.choices( 30 | ['number', 'string', 'array', 'object'], 31 | [1, 1, 1, 1])[0] 32 | return globals()[f'gen_{typ}'](depth + 1) 33 | 34 | def gen_array(depth): 35 | size = int(np.random.binomial(2 * SCALE / (2 ** (depth - 1)), 0.5)) 36 | return [ 37 | gen_value(depth + 1) for _ in range(size) 38 | ] 39 | 40 | def gen_object(depth): 41 | size = int(np.random.binomial(2 * SCALE / (2 ** (depth - 1)), 0.5)) 42 | return { 43 | gen_key(depth + 1) : gen_value(depth + 1) for _ in range(size) 44 | } 45 | 46 | def gen_json(depth=0): 47 | return gen_object(depth + 1) 48 | 49 | 50 | with open(sys.argv[1], 'w') as f: 51 | json.dump(gen_json(), f) 52 | -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "buffer.h" 3 | #include "util.h" 4 | 5 | buffer mk_buffer(unsigned capacity) { 6 | assert(capacity > 0); 7 | buffer buf; 8 | buf.raw_size = 0; 9 | buf.capacity = capacity; 10 | buf.data = (char *)malloc(buf.capacity); 11 | return buf; 12 | }; 13 | 14 | buffer mk_string(unsigned capacity) { 15 | buffer buf = mk_buffer(capacity); 16 | buf.raw_size = 1; 17 | buf.data[0] = '\0'; 18 | return buf; 19 | } 20 | 21 | void buffer_realloc(buffer *buf, unsigned capacity) { 22 | buf->data = (char *)realloc(buf->data, capacity); 23 | buf->capacity = capacity; 24 | } 25 | 26 | void string_clear(buffer *buf) { 27 | assert(buf); 28 | buf->data[0] = '\0'; 29 | buf->raw_size = 1; 30 | } 31 | 32 | static inline void buffer_request_size(buffer *buf, unsigned new_size) { 33 | if (new_size * 2 >= buf->capacity) { 34 | while (new_size * 2 >= buf->capacity) 35 | buf->capacity *= 2; 36 | buf->data = (char *)realloc(buf->data, buf->capacity); 37 | } 38 | } 39 | 40 | void buffer_putchar(buffer *buf, char c) { 41 | buffer_request_size(buf, buf->raw_size + 1); 42 | buf->data[buf->raw_size] = c; 43 | buf->raw_size++; 44 | } 45 | 46 | void buffer_append(buffer *buf, const char *data, unsigned len) { 47 | assert(buf); 48 | unsigned new_size = buf->raw_size + len; 49 | buffer_request_size(buf, new_size); 50 | memcpy(&buf->data[buf->raw_size], data, len); 51 | buf->raw_size = new_size; 52 | } 53 | 54 | // assumes that the last char is '\0' and may be overwritten 55 | static inline unsigned string_space(buffer *buf, unsigned maxlen) { 56 | unsigned space = buf->capacity - buf->raw_size + 1; 57 | if (maxlen == 0) 58 | return space; 59 | else 60 | return min(space, maxlen); 61 | } 62 | 63 | // maxlen includes '\0' 64 | // returns number of characters written, excluding '\0' 65 | int string_nprintf(buffer *buf, unsigned maxlen, const char *fmt, ...) { 66 | assert(buf); 67 | assert(buf->raw_size >= 1); 68 | assert(buf->data[buf->raw_size - 1] == '\0'); 69 | 70 | va_list args, saved_args; 71 | va_start(args, fmt); 72 | va_copy(saved_args, args); 73 | int space = string_space(buf, maxlen); 74 | // nw = number of characters that were or will be written, excluding '\0' 75 | int nw = vsnprintf(&buf->data[buf->raw_size - 1], space, fmt, args); 76 | if (maxlen != 0) 77 | nw = min(nw, maxlen - 1); 78 | va_end(args); 79 | 80 | int retry = nw + 1 > space; 81 | 82 | buffer_request_size(buf, buf->raw_size + nw); 83 | 84 | space = string_space(buf, maxlen); 85 | if (retry) 86 | vsnprintf(&buf->data[buf->raw_size - 1], space, fmt, saved_args); 87 | va_end(saved_args); 88 | buf->raw_size += nw; 89 | 90 | assert(buf->raw_size * 2 < buf->capacity); 91 | assert(buf->data[buf->raw_size - 1] == '\0'); 92 | return nw; 93 | } 94 | 95 | void buffer_compact(buffer *buf) { 96 | assert(buf); 97 | buf->capacity = buf->raw_size; 98 | buf->data = (char *)realloc(buf->data, buf->capacity); 99 | } 100 | 101 | void buffer_free(buffer *buf) { 102 | assert(buf); 103 | free(buf->data); 104 | buf = NULL; 105 | } 106 | -------------------------------------------------------------------------------- /src/buffer.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef struct { 7 | char *data; 8 | unsigned raw_size, capacity; 9 | } buffer; 10 | 11 | buffer mk_buffer(unsigned capacity); 12 | 13 | buffer mk_string(unsigned capacity); 14 | 15 | void buffer_realloc(buffer *buf, unsigned capacity); 16 | 17 | void string_clear(buffer *buf); 18 | 19 | void buffer_putchar(buffer *buf, char c); 20 | 21 | void buffer_append(buffer *buf, const char *data, unsigned len); 22 | 23 | int string_nprintf(buffer *buf, unsigned maxlen, const char *fmt, ...); 24 | 25 | void buffer_compact(buffer *buf); 26 | 27 | void buffer_free(buffer *buf); 28 | -------------------------------------------------------------------------------- /src/json.c: -------------------------------------------------------------------------------- 1 | #include "json.h" 2 | 3 | void value_free(json_value value) { 4 | switch (value.kind) { 5 | case OBJECT: 6 | object_free(value.object); 7 | break; 8 | case ARRAY: 9 | array_free(value.array); 10 | break; 11 | case STRING: 12 | free(value.string); 13 | break; 14 | default: 15 | break; 16 | } 17 | } 18 | 19 | void object_free(json_object object) { 20 | for (int i = 0; i < object_size(object); i++) { 21 | json_member keyval = object_get(object, i); 22 | free(keyval.key); 23 | value_free(keyval.val); 24 | } 25 | buffer_free(&object); 26 | } 27 | 28 | void array_free(json_array array) { 29 | for (int i = 0; i < array_size(array); i++) { 30 | value_free(array_get(array, i)); 31 | } 32 | buffer_free(&array); 33 | } 34 | -------------------------------------------------------------------------------- /src/json.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "buffer.h" 6 | 7 | typedef struct json_value json_value; 8 | typedef struct json_member json_member; 9 | typedef buffer json_object; 10 | typedef buffer json_array; 11 | 12 | struct json_value { 13 | enum { 14 | NUL, 15 | OBJECT, 16 | ARRAY, 17 | STRING, 18 | NUMBER, 19 | TRUE, 20 | FALSE 21 | } kind; 22 | 23 | union { 24 | json_object object; 25 | json_array array; 26 | char *string; 27 | float number; 28 | }; 29 | }; 30 | 31 | struct json_member { 32 | char *key; 33 | json_value val; 34 | }; 35 | 36 | static inline json_value mk_object_value(json_object object) { 37 | return (json_value) { OBJECT, { .object = object } }; 38 | } 39 | 40 | static inline json_value mk_array_value(json_array array) { 41 | return (json_value) { ARRAY, { .array = array } }; 42 | } 43 | 44 | static inline json_value mk_string_value(char *string) { 45 | return (json_value) { STRING, { .string = string } }; 46 | } 47 | 48 | static inline json_value mk_number_value(float number) { 49 | return (json_value) { NUMBER, { .number = number } }; 50 | } 51 | 52 | static inline json_value mk_true_value() { 53 | return (json_value) { TRUE }; 54 | } 55 | 56 | static inline json_value mk_false_value() { 57 | return (json_value) { FALSE }; 58 | } 59 | 60 | static inline json_value mk_null_value() { 61 | return (json_value) { NUL }; 62 | } 63 | 64 | void value_free(json_value value); 65 | 66 | static inline json_object mk_object() { 67 | return mk_buffer(64); 68 | } 69 | 70 | static inline unsigned object_size(json_object object) { 71 | return object.raw_size / sizeof(json_member); 72 | } 73 | 74 | static inline json_member object_get(json_object object, int index) { 75 | return ((json_member *)object.data)[index]; 76 | } 77 | 78 | static inline void object_append(json_object *object, json_member keyval) { 79 | buffer_append(object, (const char *)&keyval, sizeof(json_member)); 80 | } 81 | 82 | void object_free(json_object object); 83 | 84 | static inline json_array mk_array() { 85 | return mk_buffer(64); 86 | }; 87 | 88 | static inline unsigned array_size(json_array array) { 89 | return array.raw_size / sizeof(json_value); 90 | } 91 | 92 | static inline json_value array_get(json_object array, int index) { 93 | return ((json_value *)array.data)[index]; 94 | } 95 | 96 | static inline void array_append(json_array *array, json_value val) { 97 | buffer_append(array, (const char *)&val, sizeof(json_value)); 98 | } 99 | 100 | void array_free(json_array array); 101 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "term.h" 12 | #include "theme.h" 13 | #include "json.h" 14 | #include "parse.h" 15 | #include "stack.h" 16 | #include "trace.h" 17 | #include "util.h" 18 | 19 | #ifdef DEBUG 20 | FILE *trace; 21 | #endif 22 | const char *input_filename; 23 | FILE *input; 24 | 25 | int term_initialized; 26 | struct termios saved_term; 27 | 28 | typedef struct { 29 | int top, left; 30 | int nrows, ncols; 31 | buffer *rows; 32 | } pane; 33 | 34 | #define NUM_VIEW_PANES 3 35 | #define NUM_PANES (NUM_VIEW_PANES + 2) 36 | struct { 37 | int nrows, ncols; 38 | union { 39 | struct { 40 | pane top_bar; 41 | pane status_bar; 42 | pane view_panes[NUM_VIEW_PANES]; 43 | }; 44 | pane panes[NUM_PANES]; 45 | }; 46 | } window; 47 | 48 | json_stack stack; 49 | 50 | int searching; 51 | char search_str[256]; 52 | 53 | #define INT_ROUND_THRES 1e-6 54 | 55 | void term_setup() { 56 | // necessary for proper wcwidth() support 57 | setlocale(LC_ALL, ""); 58 | 59 | printf(ALT_BUF_EN); 60 | printf(CURS_HIDE); 61 | printf(TRACKING_EN); 62 | struct termios term; 63 | tcgetattr(STDIN_FILENO, &term); 64 | saved_term = term; 65 | term.c_lflag &= ~ECHO; 66 | term.c_lflag &= ~ICANON; 67 | tcsetattr(STDIN_FILENO, TCSAFLUSH, &term); 68 | term_initialized = 1; 69 | } 70 | 71 | static void reallocate_rows(pane *p, int nrows) { 72 | for (int i = 0; i < p->nrows; i++) 73 | buffer_free(&p->rows[i]); 74 | p->nrows = nrows; 75 | free(p->rows); 76 | p->rows = calloc(nrows, sizeof(buffer)); 77 | for (int i = 0; i < p->nrows; i++) { 78 | // up to 4 bytes per (unicode) character + 32 formatting characters 79 | unsigned capacity = 4 * p->ncols + 32 + 1; 80 | p->rows[i] = mk_buffer(capacity); 81 | string_clear(&p->rows[i]); 82 | } 83 | } 84 | 85 | int print_row(buffer *dest, const char* key, int index, json_value value, 86 | int max_cols, int selected, int unfolded); 87 | 88 | void pane_resize() { 89 | struct winsize wsize; 90 | ioctl(STDIN_FILENO, TIOCGWINSZ, &wsize); 91 | window.nrows = wsize.ws_row; 92 | window.ncols = wsize.ws_col; 93 | 94 | TRACE("nrows: %d, ncols: %d\n", window.nrows, window.ncols); 95 | 96 | // top bar 97 | pane *tb = &window.top_bar; 98 | tb->top = 0; 99 | tb->left = 0; 100 | tb->ncols = window.ncols; 101 | reallocate_rows(tb, 1); 102 | 103 | // status bar 104 | pane *sb = &window.status_bar; 105 | sb->top = window.nrows - 1; 106 | sb->left = 0; 107 | sb->ncols = window.ncols; 108 | reallocate_rows(sb, 1); 109 | 110 | int num_view_panes = min(stack.size, NUM_VIEW_PANES); 111 | for (int i = 0, col = 0; i < num_view_panes; i++) { 112 | pane *p = &window.view_panes[i]; 113 | p->top = 2; 114 | p->left = col; 115 | reallocate_rows(p, window.nrows - 4); 116 | 117 | int rem_panes = num_view_panes - i; 118 | p->ncols = (window.ncols - col) / rem_panes - 1; 119 | 120 | // try to shrink p->ncols 121 | if (i < num_view_panes - 1) { 122 | json_pos *pos = stack_peekn(&stack, num_view_panes - 1 - i); 123 | json_value val = pos->value; 124 | if (val.kind == OBJECT) { 125 | int rows = min(p->nrows, object_size(val.array)); 126 | int longest_row = 0; 127 | for (int di = 0; di < rows; di++) { 128 | json_member memb = object_get(val.object, di); 129 | int cols = print_row( 130 | &p->rows[di], memb.key, di, memb.val, p->ncols, 0, 0); 131 | longest_row = max(longest_row, cols); 132 | } 133 | p->ncols = min(longest_row + 1, p->ncols); 134 | } 135 | else if (val.kind == ARRAY) { 136 | int rows = min(p->nrows, array_size(val.array)); 137 | int longest_row = 0; 138 | for (int di = 0; di < rows; di++) { 139 | json_value elt = array_get(val.object, di); 140 | int cols = print_row( 141 | &p->rows[di], "", di, elt, p->ncols, 0, 0); 142 | longest_row = max(longest_row, cols); 143 | } 144 | p->ncols = min(longest_row + 1, p->ncols); 145 | } 146 | } 147 | col += p->ncols + 1; 148 | } 149 | } 150 | 151 | // https://en.wikipedia.org/wiki/UTF-8#Encoding 152 | static inline wchar_t utf8_decode(const unsigned char *c) { 153 | if (c[0] < 0b11000000) 154 | return c[0]; 155 | if (c[0] < 0b11100000) 156 | return (c[0] & 0b00011111) << 6 | 157 | (c[1] & 0b00111111); 158 | else if (c[0] < 0b11110000) 159 | return (c[0] & 0b00001111) << 12 | 160 | (c[1] & 0b00111111) << 6 | 161 | (c[2] & 0b00111111); 162 | else 163 | return (c[0] & 0b00000111) << 18 | 164 | (c[1] & 0b00111111) << 12 | 165 | (c[2] & 0b00111111) << 6 | 166 | (c[3] & 0b00111111); 167 | } 168 | 169 | typedef struct { 170 | int cols, num_read; 171 | } print_cols_r; 172 | 173 | int wcwidth(wchar_t); 174 | 175 | // returns the number of columns expected to be occupied 176 | print_cols_r print_cols(buffer *dest, const char *src, int num_cols, 177 | int escape) { 178 | assert (dest->raw_size >= 1 && dest->data[dest->raw_size - 1] == '\0'); 179 | dest->raw_size--; 180 | 181 | int cols = 0; 182 | int i = 0; 183 | for (; src[i] != '\0' && cols < num_cols; ) { 184 | unsigned char c = src[i]; 185 | if (escape && c == '\n') { 186 | buffer_putchar(dest, '\\'); 187 | buffer_putchar(dest, 'n'); 188 | i++; 189 | cols += 2; 190 | } 191 | else if (escape && c == '\r') { 192 | buffer_putchar(dest, '\\'); 193 | buffer_putchar(dest, 'r'); 194 | i++; 195 | cols += 2; 196 | } 197 | else if (escape && c == '\t') { 198 | buffer_putchar(dest, '\\'); 199 | buffer_putchar(dest, 't'); 200 | i++; 201 | cols += 2; 202 | } 203 | else if (c == '\n') { 204 | i++; 205 | break; 206 | } 207 | else if (c <= 0x7f) { 208 | buffer_putchar(dest, src[i++]); 209 | cols++; 210 | } 211 | else { // UTF-8 212 | wchar_t wc = utf8_decode( 213 | (const unsigned char *)&src[i]); 214 | if (c < 0xe0) { 215 | buffer_putchar(dest, src[i++]); 216 | buffer_putchar(dest, src[i++]); 217 | } 218 | else if (c < 0xf0) { 219 | buffer_putchar(dest, src[i++]); 220 | buffer_putchar(dest, src[i++]); 221 | buffer_putchar(dest, src[i++]); 222 | } 223 | else { 224 | buffer_putchar(dest, src[i++]); 225 | buffer_putchar(dest, src[i++]); 226 | buffer_putchar(dest, src[i++]); 227 | buffer_putchar(dest, src[i++]); 228 | } 229 | cols += wcwidth(wc); 230 | } 231 | } 232 | buffer_putchar(dest, '\0'); 233 | return (print_cols_r){cols, i}; 234 | } 235 | 236 | void print_cur_pos(buffer *dest, int cols) { 237 | string_nprintf(dest, 0, FMT_BOLD); 238 | for (int i = 0; i < stack.size - 1 && cols > 0; i++) { 239 | json_value value = stack.data[i].value; 240 | int index = stack.data[i].index; 241 | switch (value.kind) { 242 | case OBJECT: { 243 | const char* key = object_get(value.object, index).key; 244 | cols -= string_nprintf(dest, cols + 1, "%c", '.'); 245 | cols -= print_cols(dest, key, cols, 1).cols; 246 | break; 247 | } 248 | case ARRAY: 249 | cols -= string_nprintf(dest, cols + 1, "[%d]", index); 250 | break; 251 | default: 252 | assert(0); 253 | } 254 | } 255 | string_nprintf(dest, 0, FMT_RESET); 256 | } 257 | 258 | int summarize_value(buffer *dest, json_value value, int cols, int unfolded) { 259 | switch (value.kind) { 260 | case OBJECT: 261 | if (object_size(value.object) == 0) 262 | return string_nprintf(dest, cols + 1, "{}"); 263 | else if (!unfolded) 264 | return string_nprintf(dest, cols + 1, "{..}"); 265 | else 266 | return 0; 267 | case ARRAY: 268 | if (array_size(value.object) == 0) 269 | return string_nprintf(dest, cols + 1, "[]"); 270 | else if (!unfolded) 271 | return string_nprintf(dest, cols + 1, "[..]"); 272 | else 273 | return 0; 274 | case STRING: 275 | return print_cols(dest, value.string, cols, 1).cols; 276 | case NUMBER: 277 | if ((value.number - (int)value.number) < INT_ROUND_THRES) 278 | return string_nprintf(dest, cols + 1, "%d", (int)value.number); 279 | else 280 | return string_nprintf(dest, cols + 1, "%f", value.number); 281 | case TRUE: 282 | return string_nprintf(dest, cols + 1, "true"); 283 | case FALSE: 284 | return string_nprintf(dest, cols + 1, "false"); 285 | case NUL: 286 | return string_nprintf(dest, cols + 1, "null"); 287 | default: 288 | return 0; 289 | } 290 | } 291 | 292 | void append_spaces(buffer *dest, int num) { 293 | dest->raw_size--; 294 | for (; num > 0; num--) 295 | buffer_putchar(dest, ' '); 296 | buffer_putchar(dest, '\0'); 297 | } 298 | 299 | // prints an element when key == "", a member otherwise 300 | int print_row(buffer *dest, const char* key, int index, json_value value, 301 | int max_cols, int selected, int unfolded) { 302 | int cols = max_cols; 303 | if (selected) 304 | string_nprintf(dest, 0, ROW_SEL_BG ROW_SEL_FG); 305 | string_nprintf(dest, 0, FMT_BOLD); 306 | 307 | if (strlen(key) == 0) 308 | cols -= string_nprintf(dest, cols + 1, "%d ", index); 309 | else { 310 | cols -= print_cols(dest, key, cols, 1).cols; 311 | cols -= string_nprintf(dest, cols + 1, " "); 312 | } 313 | assert(dest->data[dest->raw_size - 1] == '\0'); 314 | 315 | string_nprintf(dest, 0, FMT_NOBOLD); 316 | 317 | if (cols > 0) 318 | cols -= summarize_value(dest, value, cols, unfolded); 319 | assert(dest->data[dest->raw_size - 1] == '\0'); 320 | 321 | int used_cols = max_cols - cols; 322 | 323 | if (cols > 0) 324 | append_spaces(dest, cols); 325 | 326 | string_nprintf(dest, 0, FMT_RESET); 327 | return used_cols; 328 | } 329 | 330 | // return number of rows printed 331 | int print_value(buffer *dest, json_value value, int rows, int cols, int indent) { 332 | if (rows <= 0 || indent >= cols) 333 | return 0; 334 | switch (value.kind) { 335 | case OBJECT: 336 | if (object_size(value.object) == 0) { 337 | if (indent > 0) 338 | return 0; 339 | append_spaces(&dest[0], indent); 340 | string_nprintf(&dest[0], 0, FMT_ITALIC); 341 | string_nprintf(&dest[0], cols, ""); 342 | string_nprintf(&dest[0], 0, FMT_RESET); 343 | return 1; 344 | } 345 | else { 346 | int ri = 0; 347 | for (int di = 0; ri < rows && di < object_size(value.object); di++) { 348 | json_member memb = object_get(value.object, di); 349 | append_spaces(&dest[ri], indent); 350 | print_row(&dest[ri++], memb.key, di, memb.val, cols - indent, 0, 1); 351 | if (memb.val.kind == OBJECT || memb.val.kind == ARRAY) { 352 | ri += print_value(&dest[ri], memb.val, rows - ri, 353 | cols, indent + 1); 354 | } 355 | } 356 | return ri; 357 | } 358 | case ARRAY: 359 | if (array_size(value.array) == 0) { 360 | if (indent > 0) 361 | return 0; 362 | append_spaces(&dest[0], indent); 363 | string_nprintf(&dest[0], 0, FMT_ITALIC); 364 | string_nprintf(&dest[0], cols, ""); 365 | string_nprintf(&dest[0], 0, FMT_RESET); 366 | return 1; 367 | } 368 | else { 369 | int ri = 0; 370 | for (int di = 0; ri < rows && di < array_size(value.array); di++) { 371 | json_value elt = array_get(value.array, di); 372 | append_spaces(&dest[ri], indent); 373 | print_row(&dest[ri++], "", di, elt, cols - indent, 0, 1); 374 | if (elt.kind == OBJECT || elt.kind == ARRAY) { 375 | ri += print_value(&dest[ri], elt, rows - ri, cols, 376 | indent + 1); 377 | } 378 | } 379 | return ri; 380 | } 381 | default: 382 | append_spaces(&dest[0], indent); 383 | summarize_value(&dest[0], value, cols, 0); 384 | return 1; 385 | } 386 | } 387 | 388 | int get_num_items(json_value value) { 389 | switch (value.kind) { 390 | case OBJECT: 391 | return object_size(value.object); 392 | case ARRAY: 393 | return array_size(value.array); 394 | default: 395 | return 1; 396 | } 397 | } 398 | 399 | int get_row_off(pane *p, int num_items, int index) { 400 | if (num_items <= p->nrows) 401 | return 0; 402 | int scroll_lim = p->nrows / 2 + 1; 403 | int off = index <= scroll_lim ? 0 : index - scroll_lim; 404 | return off; 405 | } 406 | 407 | void populate_view(pane *p, json_pos pos, int is_top) { 408 | json_value value = pos.value; 409 | int index = pos.index; 410 | int off = get_row_off(p, get_num_items(value), index); 411 | int curs_ri = -off + index; 412 | switch (value.kind) { 413 | case OBJECT: 414 | if (is_top) 415 | print_value(p->rows, value, p->nrows, p->ncols, 0); 416 | else { 417 | for (int ri = 0, di = off; 418 | ri < p->nrows && di < object_size(value.object); 419 | ri++, di++) { 420 | json_member memb = object_get(value.object, di); 421 | print_row(&p->rows[ri], memb.key, di, memb.val, p->ncols, 422 | !is_top && ri == curs_ri, 0); 423 | } 424 | } 425 | break; 426 | case ARRAY: 427 | if (is_top) 428 | print_value(p->rows, value, p->nrows, p->ncols, 0); 429 | else { 430 | for (int ri = 0, di = off; 431 | ri < p->nrows && di < array_size(value.array); 432 | ri++, di++) { 433 | json_value elt = array_get(value.array, di); 434 | print_row(&p->rows[ri], "", di, elt, p->ncols, 435 | !is_top && ri == curs_ri, 0); 436 | } 437 | } 438 | break; 439 | case STRING: { 440 | assert(is_top); 441 | char *s = value.string; 442 | if (s[0] == '\0') { 443 | string_nprintf(&p->rows[0], p->ncols + 1 + 8, 444 | FMT_ITALIC "" FMT_RESET); 445 | break; 446 | } 447 | for (int i = 0, ri = 0; ri < p->nrows && s[i] != '\0'; ri++) { 448 | s += print_cols(&p->rows[ri], &s[i], p->ncols, 0).num_read; 449 | } 450 | break; 451 | } 452 | default: 453 | assert(is_top); 454 | summarize_value(&p->rows[0], value, p->ncols, 0); 455 | } 456 | } 457 | 458 | void draw_pane(pane *p) { 459 | for (int n = 0; n < p->nrows; n++) { 460 | if (p->rows[n].raw_size > 1) { 461 | printf(CUP("%d", "%d"), p->top + n + 1, p->left + 1); 462 | printf("%s", p->rows[n].data); 463 | } 464 | } 465 | } 466 | 467 | void draw() { 468 | // clear existing data 469 | string_clear(&window.top_bar.rows[0]); 470 | string_clear(&window.status_bar.rows[0]); 471 | for (int i = 0; i < NUM_VIEW_PANES; i++) 472 | for (int ri = 0; ri < window.view_panes[i].nrows; ri++) 473 | string_clear(&window.view_panes[i].rows[ri]); 474 | 475 | // fill each pane with corresponding data 476 | if (!searching) 477 | string_nprintf(&window.status_bar.rows[0], window.status_bar.ncols + 1, 478 | "%s", input_filename); 479 | else 480 | string_nprintf(&window.status_bar.rows[0], window.status_bar.ncols + 1, 481 | "/%s", search_str); 482 | print_cur_pos(&window.top_bar.rows[0], window.top_bar.ncols); 483 | assert(stack.size >= 1); 484 | int num_view_panes = min(stack.size, NUM_VIEW_PANES); 485 | for (int i = num_view_panes - 1, si = 0; i >= 0; i--, si++) { 486 | populate_view(&window.view_panes[i], *stack_peekn(&stack, si), si == 0); 487 | } 488 | 489 | printf(ED("2")); 490 | for (int i = 0; i < NUM_PANES; i++) { 491 | draw_pane(&window.panes[i]); 492 | } 493 | fflush(STDIN_FILENO); 494 | } 495 | 496 | void move_to_parent() { 497 | if (stack.size > 2) 498 | stack_pop(&stack); 499 | } 500 | 501 | void move_to_child() { 502 | json_pos *cur = stack_peek(&stack); 503 | switch (cur->value.kind) { 504 | json_value next; 505 | case OBJECT: 506 | if (object_size(cur->value.object) > 0) { 507 | next = object_get(cur->value.object, cur->index).val; 508 | stack_push(&stack, (json_pos){ next, 0 }); 509 | } 510 | break; 511 | case ARRAY: 512 | if (array_size(cur->value.array) > 0) { 513 | next = array_get(cur->value.object, cur->index); 514 | stack_push(&stack, (json_pos){ next, 0 }); 515 | } 516 | break; 517 | default: 518 | break; 519 | } 520 | } 521 | 522 | void move_to_next(int off) { 523 | if (stack.size > 1) { 524 | stack_pop(&stack); 525 | json_pos *cur = stack_peek(&stack); 526 | int max_idx = 0; 527 | switch (cur->value.kind) { 528 | case OBJECT: 529 | max_idx = object_size(cur->value.object) - 1; 530 | break; 531 | case ARRAY: 532 | max_idx = array_size(cur->value.array) - 1; 533 | break; 534 | default: 535 | break; 536 | } 537 | cur->index = min(max(cur->index + off, 0), max_idx); 538 | move_to_child(); 539 | } 540 | } 541 | 542 | void search_next(int rev) { 543 | json_stack search_stack = stack; 544 | search(&search_stack, search_str, rev); 545 | if (search_stack.size > 0) 546 | stack = search_stack; 547 | } 548 | 549 | void handle_mouse_press(int x, int y) { 550 | TRACE("Mouse %d, %d\n", x, y); 551 | // find which pane was clicked 552 | int num_view_panes = min(stack.size, NUM_VIEW_PANES); 553 | int pi = -1; 554 | for (int i = 0; i < num_view_panes; i++) { 555 | pane *p = &window.view_panes[i]; 556 | TRACE("Pane x=[%d, %d), y=[%d, %d)\n", p->left, p->left + p->ncols, p->top, p->top + p->nrows); 557 | if (p->left <= x && x < p->left + p->ncols && 558 | p->top <= y && y < p->top + p->nrows) { 559 | TRACE("Pane %d clicked\n", i); 560 | pi = i; 561 | break; 562 | } 563 | } 564 | if (pi == -1) 565 | return; 566 | 567 | int ri = y - window.view_panes[pi].top; 568 | int si = num_view_panes - 1 - pi; 569 | 570 | TRACE("ri = %d, si = %d\n", ri, si); 571 | 572 | // handle right-most pane 573 | if (si == 0) { 574 | // TODO: navigate to item that was actually clicked 575 | move_to_child(); 576 | return; 577 | } 578 | 579 | // get total rows 580 | json_pos *pos = stack_peekn(&stack, si); 581 | json_value val = pos->value; 582 | int tot_rows = 0; 583 | if (val.kind == OBJECT) 584 | tot_rows = object_size(val.object); 585 | else if (val.kind == ARRAY) 586 | tot_rows = array_size(val.array); 587 | if (ri >= tot_rows) 588 | return; 589 | 590 | // unwind stack 591 | stack_pop(&stack); 592 | for (int i = 0; i < si - 1; i++) 593 | stack_pop(&stack); 594 | 595 | int num_items = get_num_items(stack_peek(&stack)->value); 596 | int off = get_row_off(&window.view_panes[pi], num_items, pos->index); 597 | TRACE("off = %d\n", off); 598 | 599 | // navigate to currently selected unfolded item 600 | if (stack_peek(&stack)->index == off + ri && si == 1) { 601 | move_to_child(); 602 | move_to_child(); 603 | } 604 | // move to another item 605 | else { 606 | stack_peek(&stack)->index = off + ri; 607 | move_to_child(); 608 | 609 | for (int i = 0; i < si - 2; i++) 610 | move_to_child(); 611 | } 612 | } 613 | 614 | void loop() { 615 | while (1) { 616 | char in[6]; 617 | int num_read = read(STDIN_FILENO, &in, 6); 618 | switch (in[0]) { 619 | case '\x1b': 620 | if (num_read == 1) { // ESC key 621 | if (!searching) 622 | return; 623 | else { 624 | searching = 0; 625 | search_str[0] = '\0'; 626 | draw(); 627 | break; 628 | } 629 | } 630 | if (num_read >= 3 && in[1] == '[') { 631 | switch (in[2]) { 632 | case KEY_UP: 633 | move_to_next(-1); 634 | draw(); 635 | break; 636 | case KEY_DOWN: 637 | move_to_next(1); 638 | draw(); 639 | break; 640 | case KEY_RIGHT: 641 | move_to_child(); 642 | pane_resize(); 643 | draw(); 644 | break; 645 | case KEY_LEFT: 646 | move_to_parent(); 647 | pane_resize(); 648 | draw(); 649 | break; 650 | case '5': 651 | if (in[3] == '~') { // PgUp 652 | move_to_next(-window.view_panes[0].nrows); 653 | draw(); 654 | } 655 | break; 656 | case '6': 657 | if (in[3] == '~') { // PgDown 658 | move_to_next(window.view_panes[0].nrows); 659 | draw(); 660 | } 661 | break; 662 | case '7': 663 | case 'H': 664 | if (in[3] == '~') { // Home 665 | move_to_next(INT_MIN); 666 | draw(); 667 | } 668 | break; 669 | case '8': 670 | case 'F': 671 | if (in[3] == '~') { // End 672 | move_to_next(INT_MAX); 673 | draw(); 674 | } 675 | break; 676 | case 'M': { 677 | char b = in[3] - 32; 678 | char Cx = in[4] - 32, Cy = in[5] - 32; 679 | if (b == 0) { // Left button 680 | handle_mouse_press(Cx - 1, Cy - 1); 681 | pane_resize(); 682 | draw(); 683 | } 684 | else if (b == 64) { // Mouse wheel up 685 | move_to_next(-1); 686 | draw(); 687 | } 688 | else if (b == 65) { // Mouse wheel down 689 | move_to_next(1); 690 | draw(); 691 | } 692 | break; 693 | } 694 | } 695 | } 696 | break; 697 | case '\x7f': // Backspace key 698 | if (!searching) { 699 | move_to_parent(); 700 | pane_resize(); 701 | } 702 | else if (search_str[0] != '\0') { 703 | int i; 704 | for (i = 0; search_str[i] != '\0'; i++); 705 | search_str[i-1] = '\0'; 706 | } 707 | draw(); 708 | break; 709 | case '\x0a': // Enter key 710 | if (!searching) 711 | move_to_child(); 712 | else { 713 | searching = 0; 714 | search_next(0); 715 | } 716 | pane_resize(); 717 | draw(); 718 | break; 719 | case '/': 720 | search_str[0] = '\0'; 721 | searching = 1; 722 | draw(); 723 | break; 724 | case 'n': { 725 | if (!searching) { 726 | if (search_str[0] == '\0') 727 | break; 728 | search_next(0); 729 | pane_resize(); 730 | draw(); 731 | break; 732 | } 733 | // fallthrough 734 | } 735 | case 'b': 736 | case 'N': { 737 | if (!searching) { 738 | if (search_str[0] == '\0') 739 | break; 740 | search_next(1); 741 | pane_resize(); 742 | draw(); 743 | break; 744 | } 745 | // fallthrough 746 | } 747 | case 'q': 748 | if (!searching) 749 | return; 750 | // fallthrough 751 | default: { 752 | if (searching) { 753 | int i; 754 | for (i = 0; search_str[i] != '\0'; i++); 755 | search_str[i] = in[0]; 756 | search_str[i+1] = '\0'; 757 | draw(); 758 | } 759 | } 760 | 761 | } 762 | } 763 | } 764 | 765 | 766 | void on_int(int i) { 767 | exit(128 + SIGINT); 768 | } 769 | 770 | void on_term(int i) { 771 | exit(128 + SIGTERM); 772 | } 773 | 774 | void on_resize() { 775 | pane_resize(); 776 | draw(); 777 | signal(SIGWINCH, on_resize); 778 | } 779 | 780 | void fin() { 781 | json_value top = stack_peekn(&stack, stack.size - 1)->value; 782 | value_free(top); 783 | 784 | for (int i = 0; i < NUM_PANES; i++) { 785 | pane *p = &window.panes[i]; 786 | for (int j = 0; j < p->nrows; j++) 787 | buffer_free(&p->rows[j]); 788 | free(p->rows); 789 | } 790 | 791 | if (term_initialized) { 792 | tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_term); 793 | printf(CURS_SHOW); 794 | printf(ALT_BUF_DIS); 795 | printf(TRACKING_DIS); 796 | } 797 | 798 | #ifdef DEBUG 799 | if (trace) 800 | fclose(trace); 801 | #endif 802 | } 803 | 804 | 805 | int main(int argc, char **argv) { 806 | if (argc != 2) { 807 | fprintf(stderr, "Usage: %s \n", argv[0]); 808 | exit(EXIT_FAILURE); 809 | } 810 | input_filename = argv[1]; 811 | 812 | if(!isatty(STDIN_FILENO)){ 813 | fprintf(stderr, "Not a terminal\n"); 814 | exit(EXIT_FAILURE); 815 | } 816 | 817 | atexit(fin); 818 | // TODO: use sigaction for all signal handling 819 | signal(SIGINT, on_term); 820 | signal(SIGTERM, on_term); 821 | #ifndef DEBUG 822 | signal(SIGABRT, on_term); 823 | #endif 824 | 825 | #ifdef DEBUG 826 | trace = fopen("trace.txt", "w"); 827 | #endif 828 | 829 | FILE *f = fopen(input_filename, "r"); 830 | if (!f) { 831 | fprintf(stderr, "Error reading input file\n"); 832 | exit(EXIT_FAILURE); 833 | } 834 | parse_result pr = parse_json(f); 835 | fclose(f); 836 | if (!pr.success) { 837 | print_error(stderr, pr); 838 | exit(EXIT_FAILURE); 839 | } 840 | 841 | json_value top = pr.res; 842 | 843 | stack_push(&stack, (json_pos){top, 0}); 844 | move_to_child(); 845 | 846 | term_setup(); 847 | pane_resize(); 848 | signal(SIGWINCH, on_resize); 849 | 850 | draw(); 851 | 852 | loop(); 853 | } 854 | -------------------------------------------------------------------------------- /src/parse.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "parse.h" 5 | #include "trace.h" 6 | 7 | typedef struct parse_error parse_error; 8 | typedef struct parse_state parse_state; 9 | 10 | struct parse_state { 11 | FILE *f; 12 | int consumed; 13 | int line, col; 14 | char tok; 15 | jmp_buf on_err; 16 | }; 17 | 18 | static inline char cur(const parse_state *ps) { 19 | return ps->tok; 20 | } 21 | 22 | static void advance(parse_state *ps) { 23 | if (ps->tok == '\n') { 24 | ps->line++; 25 | ps->col = 1; 26 | } 27 | else { 28 | ps->col++; 29 | } 30 | ps->tok = fgetc(ps->f); 31 | ps->consumed++; 32 | } 33 | 34 | // FIXME: this needs to free allocated memory 35 | static void error(parse_state *ps) { 36 | longjmp(ps->on_err, 1); 37 | } 38 | 39 | static inline void tracep(parse_state *ps, const char *msg) { 40 | #ifdef DEBUG_PARSE 41 | TRACE("%5d,%3d ", ps->line, ps->col); 42 | char c = cur(ps); 43 | if (c == '\n') 44 | TRACE("\\n %16s\n", msg); 45 | else if (c == '\n') 46 | TRACE("\\r %16s\r", msg); 47 | else if (c == '\n') 48 | TRACE("\\t %16s\t", msg); 49 | else 50 | TRACE("%2c %16s\n", c, msg); 51 | #endif 52 | } 53 | 54 | static inline int peek(parse_state *ps, char tok) { 55 | return ps->tok == tok; 56 | } 57 | 58 | static int peek_anyof(parse_state *ps, char *tok_set) { 59 | for (char *c = tok_set; *c != '\0'; c++) { 60 | if (cur(ps) == *c) 61 | return *c; 62 | } 63 | return 0; 64 | } 65 | 66 | static int consume(parse_state *ps, char tok) { 67 | if (peek(ps, tok)) { 68 | tracep(ps, ""); 69 | advance(ps); 70 | return 1; 71 | } 72 | return 0; 73 | } 74 | 75 | static char consume_anyof(parse_state *ps, char *tok_set) { 76 | char c; 77 | if ((c = peek_anyof(ps, tok_set))) { 78 | tracep(ps, ""); 79 | advance(ps); 80 | return c; 81 | } 82 | return 0; 83 | } 84 | 85 | static int parse_char(parse_state *ps, char tok) { 86 | tracep(ps, "char"); 87 | if (consume(ps, tok)) 88 | return 1; 89 | error(ps); 90 | return 0; 91 | } 92 | 93 | static int parse_anyof(parse_state *ps, char *tok_str) { 94 | tracep(ps, "char"); 95 | char c; 96 | if ((c = consume_anyof(ps, tok_str))) 97 | return c; 98 | error(ps); 99 | return 0; 100 | } 101 | 102 | static json_value parse_top(parse_state *); 103 | static json_value parse_value(parse_state *); 104 | static json_object parse_object(parse_state *); 105 | static json_object parse_members(parse_state *); 106 | static json_member parse_member(parse_state *); 107 | static json_array parse_array(parse_state *); 108 | static json_array parse_elements(parse_state *); 109 | static json_value parse_element(parse_state *); 110 | static char *parse_string(parse_state *); 111 | static char *parse_characters(parse_state *); 112 | static int parse_character(parse_state *, char *); 113 | static int parse_escape(parse_state *, char *); 114 | static char parse_hex(parse_state *); 115 | static float parse_number(parse_state *); 116 | static int parse_integer(parse_state *); 117 | static int parse_pos_integer(parse_state *ps); 118 | static int parse_digits(parse_state *); 119 | static int parse_digit(parse_state *); 120 | static int parse_fraction(parse_state *); 121 | static int parse_exponent(parse_state *); 122 | static int parse_sign(parse_state *); 123 | static void parse_ws(parse_state *); 124 | static void parse_true(parse_state *); 125 | static void parse_false(parse_state *); 126 | static void parse_null(parse_state *); 127 | 128 | static json_value parse_top(parse_state *ps) { 129 | tracep(ps, "json"); 130 | json_value res = parse_element(ps); 131 | parse_char(ps, EOF); 132 | return res; 133 | } 134 | 135 | static json_value parse_value(parse_state *ps) { 136 | tracep(ps, "value"); 137 | if (peek(ps, '{')) 138 | return mk_object_value(parse_object(ps)); 139 | else if (peek(ps, '[')) 140 | return mk_array_value(parse_array(ps)); 141 | else if (peek(ps, '\"')) 142 | return mk_string_value(parse_string(ps)); 143 | else if (peek_anyof(ps, "0123456789-")) 144 | return mk_number_value(parse_number(ps)); 145 | else if (peek(ps, 't')) { 146 | parse_true(ps); 147 | return mk_true_value(); 148 | } 149 | else if (peek(ps, 'f')) { 150 | parse_false(ps); 151 | return mk_false_value(); 152 | } 153 | else if (peek(ps, 'n')) { 154 | parse_null(ps); 155 | return mk_null_value(); 156 | } 157 | else { 158 | error(ps); 159 | return mk_null_value(); 160 | } 161 | } 162 | 163 | static json_object parse_object(parse_state *ps) { 164 | tracep(ps, "object"); 165 | parse_char(ps, '{'); 166 | parse_ws(ps); 167 | if (consume(ps, '}')) 168 | return mk_object(); 169 | else { 170 | json_object object = parse_members(ps); 171 | parse_char(ps, '}'); 172 | return object; 173 | } 174 | } 175 | 176 | static json_object parse_members(parse_state *ps) { 177 | tracep(ps, "members"); 178 | json_object res = mk_object(); 179 | object_append(&res, parse_member(ps)); 180 | while (consume(ps, ',')) 181 | object_append(&res, parse_member(ps)); 182 | return res; 183 | } 184 | 185 | static json_member parse_member(parse_state *ps) { 186 | tracep(ps, "member"); 187 | parse_ws(ps); 188 | char *key = parse_string(ps); 189 | parse_ws(ps); 190 | parse_char(ps, ':'); 191 | json_value val = parse_element(ps); 192 | return (json_member){ key, val }; 193 | } 194 | 195 | static json_array parse_array(parse_state *ps) { 196 | tracep(ps, "array"); 197 | parse_char(ps, '['); 198 | parse_ws(ps); 199 | if (consume(ps, ']')) 200 | return mk_array(); 201 | else { 202 | json_array array = parse_elements(ps); 203 | parse_char(ps, ']'); 204 | return array; 205 | } 206 | } 207 | 208 | static json_array parse_elements(parse_state *ps) { 209 | tracep(ps, "elements"); 210 | json_array res = mk_array(); 211 | array_append(&res, parse_element(ps)); 212 | while (consume(ps, ',')) 213 | array_append(&res, parse_element(ps)); 214 | return res; 215 | } 216 | 217 | static json_value parse_element(parse_state *ps) { 218 | tracep(ps, "element"); 219 | parse_ws(ps); 220 | json_value res = parse_value(ps); 221 | parse_ws(ps); 222 | return res; 223 | } 224 | 225 | static char *parse_string(parse_state *ps) { 226 | tracep(ps, "string"); 227 | parse_char(ps, '\"'); 228 | char *res = parse_characters(ps); 229 | parse_char(ps, '\"'); 230 | return res; 231 | } 232 | 233 | static char *parse_characters(parse_state *ps) { 234 | tracep(ps, "characters"); 235 | int str_size = 16; 236 | char *res = malloc(str_size * sizeof(char)); 237 | int i; 238 | for (i = 0; !peek(ps, '\"'); i += parse_character(ps, &res[i])) { 239 | if (i * 2 >= str_size) { 240 | str_size *= 2; 241 | res = realloc(res, str_size * sizeof(char)); 242 | } 243 | } 244 | res[i] = '\0'; 245 | //res = realloc(res, i * sizeof(char) + 1); 246 | return res; 247 | } 248 | 249 | static int parse_character(parse_state *ps, char *s) { 250 | tracep(ps, "character"); 251 | unsigned char c = (unsigned char)cur(ps); 252 | if (c == '\\') 253 | return parse_escape(ps, s); 254 | else if (c == '\"' || c < 0x20) { 255 | error(ps); 256 | return 0; 257 | } 258 | else if (c <= 0x7f) { 259 | consume(ps, c); 260 | *s = c; 261 | return 1; 262 | } 263 | // UTF-8 support 264 | else { 265 | int num_read; 266 | if (c < 0xe0) { 267 | s[0] = c; 268 | s[1] = fgetc(ps->f); 269 | num_read = 2; 270 | } 271 | else if (c < 0xf0) { 272 | s[0] = c; 273 | s[1] = fgetc(ps->f); 274 | s[2] = fgetc(ps->f); 275 | num_read = 3; 276 | } 277 | else { 278 | s[0] = c; 279 | s[1] = fgetc(ps->f); 280 | s[2] = fgetc(ps->f); 281 | s[3] = fgetc(ps->f); 282 | num_read = 4; 283 | } 284 | advance(ps); 285 | return num_read; 286 | } 287 | } 288 | 289 | // https://en.wikipedia.org/wiki/UTF-8#Encoding 290 | static inline int utf8_encode(char *dest, wchar_t src) { 291 | if (src < 0x80) { 292 | dest[0] = src; 293 | return 1; 294 | } 295 | else if (src < 0x800) { 296 | dest[0] = 0b11000000 | (src & 0x0f00) >> 6 | (src & 0x00c0) >> 6; 297 | dest[1] = 0b10000000 | (src & 0x0030) | (src & 0x000f); 298 | return 2; 299 | } 300 | else if (src < 0x10000) { 301 | dest[0] = 0b11100000 | (src & 0xf000) >> 12; 302 | dest[1] = 0b10000000 | (src & 0x0f00) >> 6 | (src & 0x00c0) >> 6; 303 | dest[2] = 0b10000000 | (src & 0x0030) | (src & 0x000f); 304 | return 3; 305 | } 306 | else { 307 | // we won't encode values larger than \uFFFF 308 | assert(0); 309 | } 310 | } 311 | 312 | static int parse_escape(parse_state *ps, char *s) { 313 | tracep(ps, "escape"); 314 | parse_char(ps, '\\'); 315 | if (consume(ps, '\"')) 316 | *s = '\"'; 317 | else if (consume(ps, '\\')) 318 | *s = '\\'; 319 | else if (consume(ps, '/')) 320 | *s = '/'; 321 | else if (consume(ps, 'b')) 322 | *s = '\b'; 323 | else if (consume(ps, 'f')) 324 | *s = '\f'; 325 | else if (consume(ps, 'n')) 326 | *s = '\n'; 327 | else if (consume(ps, 'r')) 328 | *s = '\r'; 329 | else if (consume(ps, 't')) 330 | *s = '\t'; 331 | else if (consume(ps, 'u')) { 332 | wchar_t wc = parse_hex(ps) << 12 | 333 | parse_hex(ps) << 8 | 334 | parse_hex(ps) << 4 | 335 | parse_hex(ps); 336 | return utf8_encode(s, wc); 337 | } 338 | else { 339 | error(ps); 340 | return 0; 341 | } 342 | return 1; 343 | } 344 | 345 | static char parse_hex(parse_state *ps) { 346 | tracep(ps, "hex"); 347 | char res; 348 | if ((res = consume_anyof(ps, "0123456789"))) 349 | return res - '0'; 350 | else if ((res = consume_anyof(ps, "ABCDEF"))) 351 | return 10 + res - 'A'; 352 | else if ((res = consume_anyof(ps, "abcdef"))) 353 | return 10 + res - 'a'; 354 | else { 355 | error(ps); 356 | return 0; 357 | } 358 | } 359 | 360 | static float parse_number(parse_state *ps) { 361 | tracep(ps, "number"); 362 | char s[48]; 363 | int i = parse_integer(ps); 364 | int f = parse_fraction(ps); 365 | int e = parse_exponent(ps); 366 | snprintf(s, 48, "%d.%de%d", i, f, e); 367 | return atof(s); 368 | } 369 | 370 | static int parse_integer(parse_state *ps) { 371 | tracep(ps, "integer"); 372 | int sgn = consume(ps, '-') ? -1 : 1; 373 | return sgn * parse_pos_integer(ps); 374 | } 375 | 376 | static int parse_pos_integer(parse_state *ps) { 377 | tracep(ps, "pos_integer"); 378 | if (peek(ps, '0')) 379 | return parse_digit(ps); 380 | else if (peek_anyof(ps, "123456789")) 381 | return parse_digits(ps); 382 | else { 383 | error(ps); 384 | return 0; 385 | } 386 | } 387 | 388 | static int parse_digits(parse_state *ps) { 389 | tracep(ps, "digits"); 390 | int res = parse_digit(ps); 391 | while (peek_anyof(ps, "0123456789")) { 392 | res *= 10; 393 | res += parse_digit(ps); 394 | } 395 | return res; 396 | } 397 | 398 | static int parse_digit(parse_state *ps) { 399 | tracep(ps, "digit"); 400 | return parse_anyof(ps, "0123456789") - '0'; 401 | } 402 | 403 | static int parse_fraction(parse_state *ps) { 404 | tracep(ps, "fraction"); 405 | if (consume(ps, '.')) 406 | return parse_digits(ps); 407 | return 0; 408 | } 409 | 410 | static int parse_exponent(parse_state *ps) { 411 | tracep(ps, "exponent"); 412 | if (consume(ps, 'E') || consume(ps, 'e')) { 413 | return parse_sign(ps) * parse_digits(ps); 414 | } 415 | return 0; 416 | } 417 | 418 | static int parse_sign(parse_state *ps) { 419 | tracep(ps, "sign"); 420 | if (consume(ps, '+')) 421 | return 1; 422 | else if (consume(ps, '-')) 423 | return -1; 424 | else 425 | return 1; 426 | } 427 | 428 | static void parse_ws(parse_state *ps) { 429 | tracep(ps, "ws"); 430 | while (consume_anyof(ps, " \n\r\t")); 431 | } 432 | 433 | static void parse_true(parse_state *ps) { 434 | tracep(ps, "true"); 435 | parse_char(ps, 't'); 436 | parse_char(ps, 'r'); 437 | parse_char(ps, 'u'); 438 | parse_char(ps, 'e'); 439 | } 440 | 441 | static void parse_false(parse_state *ps) { 442 | tracep(ps, "false"); 443 | parse_char(ps, 'f'); 444 | parse_char(ps, 'a'); 445 | parse_char(ps, 'l'); 446 | parse_char(ps, 's'); 447 | parse_char(ps, 'e'); 448 | } 449 | 450 | static void parse_null(parse_state *ps) { 451 | tracep(ps, "null"); 452 | parse_char(ps, 'n'); 453 | parse_char(ps, 'u'); 454 | parse_char(ps, 'l'); 455 | parse_char(ps, 'l'); 456 | } 457 | 458 | parse_result parse_json(FILE *input) { 459 | parse_state ps = { .f = input, .consumed = 0, .line = 1, .col = 0 }; 460 | advance(&ps); 461 | if (setjmp(ps.on_err)) { 462 | parse_result pe; 463 | pe.success = 0; 464 | pe.error.line = ps.line; 465 | pe.error.col = ps.col; 466 | pe.error.tok = ps.tok; 467 | return pe; 468 | } 469 | return (parse_result){ .success = 1, .res = parse_top(&ps) }; 470 | } 471 | 472 | void print_error(FILE *os, parse_result pe) { 473 | if (pe.error.tok == '\n') 474 | fprintf(stderr, "Error on line %d: unexpected end of line\n", 475 | pe.error.line); 476 | else 477 | fprintf(stderr, "Error on line %d, column %d: " 478 | "unexpected character %c\n", 479 | pe.error.line, pe.error.col, pe.error.tok); 480 | } 481 | -------------------------------------------------------------------------------- /src/parse.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "json.h" 5 | 6 | typedef struct { 7 | int success; 8 | union { 9 | json_value res; 10 | struct { 11 | int line, col; 12 | char tok; 13 | } error; 14 | }; 15 | } parse_result; 16 | 17 | parse_result parse_json(FILE *f); 18 | void print_error(FILE *os, parse_result); 19 | -------------------------------------------------------------------------------- /src/print.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "json.h" 3 | #include "print.h" 4 | 5 | static void print_indent(int count) { 6 | for (int i = 0; i < count; i++) 7 | printf(" "); 8 | } 9 | 10 | static void print_top(json_value); 11 | static void print_value(json_value, int); 12 | static void print_object(json_object, int); 13 | static void print_members(json_object, int); 14 | static void print_member(json_member, int); 15 | static void print_array(json_array, int); 16 | static void print_elements(json_array, int); 17 | static void print_element(json_value, int); 18 | static void print_string(char *); 19 | static void print_number(float); 20 | static void print_true(); 21 | static void print_false(); 22 | static void print_null(); 23 | 24 | static void print_top(json_value value) { 25 | print_value(value, 0); 26 | putchar('\n'); 27 | } 28 | 29 | static void print_value(json_value value, int indent) { 30 | switch (value.kind) { 31 | case OBJECT: 32 | print_object(value.object, indent); 33 | break; 34 | case ARRAY: 35 | print_array(value.array, indent); 36 | break; 37 | case STRING: 38 | print_string(value.string); 39 | break; 40 | case NUMBER: 41 | print_number(value.number); 42 | break; 43 | case TRUE: 44 | print_true(); 45 | break; 46 | case FALSE: 47 | print_false(); 48 | break; 49 | case NUL: 50 | print_null(); 51 | } 52 | } 53 | 54 | static void print_object(json_object object, int indent) { 55 | putchar('{'); 56 | if (object_size(object) > 0) { 57 | putchar('\n'); 58 | print_members(object, indent + 1); 59 | print_indent(indent); 60 | } 61 | putchar('}'); 62 | } 63 | 64 | static void print_members(json_object object, int indent) { 65 | for (int i = 0; i < object_size(object); i++) { 66 | print_member(object_get(object, i), indent); 67 | if (i < object_size(object) - 1) 68 | putchar(','); 69 | putchar('\n'); 70 | } 71 | } 72 | 73 | static void print_member(json_member member, int indent) { 74 | print_indent(indent); 75 | print_string(member.key); 76 | printf(": "); 77 | print_value(member.val, indent); 78 | } 79 | 80 | static void print_array(json_array array, int indent) { 81 | putchar('['); 82 | if (array_size(array) > 0) { 83 | putchar('\n'); 84 | print_elements(array, indent + 1); 85 | print_indent(indent); 86 | } 87 | putchar(']'); 88 | } 89 | 90 | static void print_elements(json_array array, int indent) { 91 | for (int i = 0; i < array_size(array); i++) { 92 | print_element(array_get(array, i), indent); 93 | if (i < array_size(array) - 1) 94 | putchar(','); 95 | putchar('\n'); 96 | } 97 | } 98 | 99 | static void print_element(json_value value, int indent) { 100 | print_indent(indent); 101 | print_value(value, indent); 102 | } 103 | 104 | static void print_string(char *s) { 105 | putchar('\"'); 106 | printf("%s", s); 107 | putchar('\"'); 108 | } 109 | 110 | static void print_number(float x) { 111 | printf("%.2f", x); 112 | } 113 | 114 | static void print_true() { 115 | printf("true"); 116 | } 117 | 118 | static void print_false() { 119 | printf("false"); 120 | } 121 | 122 | static void print_null() { 123 | printf("null"); 124 | } 125 | 126 | void print_json(json_value value) { 127 | print_top(value); 128 | } 129 | -------------------------------------------------------------------------------- /src/print.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "json.h" 5 | 6 | void print_json(json_value value); 7 | -------------------------------------------------------------------------------- /src/stack.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "json.h" 3 | #include "trace.h" 4 | 5 | #define STACK_SIZE 128 6 | 7 | typedef struct { 8 | json_value value; 9 | int index; 10 | } json_pos; 11 | 12 | typedef struct { 13 | json_pos data[STACK_SIZE]; 14 | int size; 15 | } json_stack; 16 | 17 | static inline json_pos *stack_peek(json_stack *stack) { 18 | return &stack->data[stack->size - 1]; 19 | } 20 | 21 | static inline json_pos *stack_peekn(json_stack *stack, int n) { 22 | assert(n <= stack->size); 23 | return &stack->data[stack->size - 1 - n]; 24 | } 25 | 26 | static inline json_pos stack_pop(json_stack *stack) { 27 | return stack->data[stack->size--]; 28 | } 29 | 30 | static inline void stack_push(json_stack *stack, json_pos pos) { 31 | assert(stack->size < STACK_SIZE); 32 | stack->data[stack->size++] = pos; 33 | } 34 | 35 | void trace_stack(json_stack *stack) { 36 | for (int i = 0; i < stack->size; i++) { 37 | TRACE("%d, ", stack->data[i].index); 38 | } 39 | TRACE("\n"); 40 | } 41 | 42 | static int match(const char *haystack, const char *needle) { 43 | TRACE("match: %s %s = %d\n", haystack, needle, strstr(haystack, needle) != NULL); 44 | return strstr(haystack, needle) != NULL; 45 | } 46 | 47 | static void traverse_next(json_stack *stack, int rev) { 48 | stack_pop(stack); 49 | if (stack->size > 0) { 50 | if (!rev) 51 | stack_peek(stack)->index++; 52 | else 53 | stack_peek(stack)->index--; 54 | } 55 | } 56 | 57 | static int first_index(json_value val, int rev) { 58 | if (rev && val.kind == OBJECT) 59 | return object_size(val.object) - 1; 60 | else if (rev && val.kind == ARRAY) 61 | return array_size(val.array) - 1; 62 | else 63 | return 0; 64 | } 65 | 66 | // search for str starting from (but not including) position on top of stack, 67 | // until either a match has been found or all contents have been popped 68 | void search(json_stack *stack, const char *str, int rev) { 69 | for (int i = 0; stack->size > 0; i++) { 70 | trace_stack(stack); 71 | json_pos *top = stack_peek(stack); 72 | json_value val = top->value; 73 | switch (val.kind) { 74 | case OBJECT: 75 | if (top->index >= object_size(val.object) || top->index < 0) 76 | traverse_next(stack, rev); 77 | else { 78 | json_member next = object_get(val.object, top->index); 79 | int si = first_index(next.val, rev); 80 | stack_push(stack, (json_pos){ next.val, si }); 81 | if (i > 0 && match(next.key, str)) 82 | return; 83 | } 84 | break; 85 | case ARRAY: 86 | if (top->index >= array_size(val.array) || top->index < 0) 87 | traverse_next(stack, rev); 88 | else { 89 | json_value next = array_get(val.array, top->index); 90 | int si = first_index(next, rev); 91 | stack_push(stack, (json_pos){ next, si }); 92 | } 93 | break; 94 | case STRING: 95 | if (i > 0 && match(val.string, str)) 96 | return; 97 | traverse_next(stack, rev); 98 | break; 99 | case NUMBER: { 100 | char s[32]; 101 | snprintf(s, 32, "%f", val.number); 102 | if (i > 0 && match(s, str)) 103 | return; 104 | traverse_next(stack, rev); 105 | break; 106 | } 107 | case TRUE: 108 | if (i > 0 && match("true", str)) 109 | return; 110 | traverse_next(stack, rev); 111 | break; 112 | case FALSE: 113 | if (i > 0 && match("false", str)) 114 | return; 115 | traverse_next(stack, rev); 116 | break; 117 | case NUL: 118 | if (i > 0 && match("null", str)) 119 | return; 120 | traverse_next(stack, rev); 121 | break; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/term.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // reference: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html 4 | 5 | #define ESC "\x1b" 6 | #define CSI ESC "[" 7 | 8 | #define CUU(n) CSI n "A" 9 | #define CUD(n) CSI n "B" 10 | #define CUF(n) CSI n "C" 11 | #define CUB(n) CSI n "B" 12 | #define CNL(n) CSI n "L" 13 | #define CPL(n) CSI n "P" 14 | #define CHA(n) CSI n "G" 15 | #define CUP(n, m) CSI n ";" m "H" 16 | #define ED(n) CSI n "J" 17 | #define CURS_SHOW CSI "?25h" 18 | #define CURS_HIDE CSI "?25l" 19 | #define ALT_BUF_EN CSI "?1049h" 20 | #define ALT_BUF_DIS CSI "?1049l" 21 | // "x11 xterm tracking mode" 22 | // (konsole does not support x10 compatibility mode) 23 | #define TRACKING_EN CSI "?1000h" 24 | #define TRACKING_DIS CSI "?1000l" 25 | 26 | #define SGR(n) CSI n "m" 27 | #define FMT_RESET SGR("0") 28 | #define FMT_BOLD SGR("1") 29 | #define FMT_FAINT SGR("2") 30 | #define FMT_ITALIC SGR("3") 31 | #define FMT_UNDERLINE SGR("4") 32 | #define FMT_NOBOLD SGR("22") 33 | #define FMT_FG_BLACK SGR("30") 34 | #define FMT_FG_RED SGR("31") 35 | #define FMT_FG_GREEN SGR("32") 36 | #define FMT_FG_WHITE SGR("37") 37 | #define FMT_BG_BLACK SGR("90") 38 | #define FMT_BG_RED SGR("41") 39 | #define FMT_BG_GREEN SGR("42") 40 | #define FMT_BG_WHITE SGR("47") 41 | 42 | #define KEY_UP 'A' 43 | #define KEY_DOWN 'B' 44 | #define KEY_RIGHT 'C' 45 | #define KEY_LEFT 'D' 46 | -------------------------------------------------------------------------------- /src/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "json.h" 4 | #include "parse.h" 5 | #include "print.h" 6 | #include "stack.h" 7 | 8 | #ifdef DEBUG 9 | FILE *trace; 10 | #endif 11 | 12 | void parse_round_trip_test(FILE *f) { 13 | parse_result pr = parse_json(f); 14 | if (pr.success) { 15 | //print_json(pr.res); 16 | value_free(pr.res); 17 | } 18 | else { 19 | print_error(stderr, pr); 20 | } 21 | } 22 | 23 | void data_struct_test() { 24 | json_array array = mk_array(); 25 | for (int i = 0; i < 32; i++) 26 | array_append(&array, mk_number_value((float)i)); 27 | json_object object = mk_object(); 28 | for (int i = 0; i < 10; i++) 29 | object_append(&object, 30 | (json_member){"elements", mk_array_value(array)}); 31 | print_json(mk_object_value(object)); 32 | 33 | buffer s = mk_string(16); 34 | assert(strncmp(s.data, "", s.raw_size) == 0); 35 | buffer_free(&s); 36 | 37 | s = mk_string(16); 38 | string_nprintf(&s, 0, "Hello world!\n"); 39 | assert(s.raw_size == 14 && s.capacity == 32); 40 | assert(strncmp(s.data, "Hello world!\n", s.raw_size) == 0); 41 | string_clear(&s); 42 | assert(strncmp(s.data, "", s.raw_size) == 0); 43 | buffer_free(&s); 44 | 45 | s = mk_string(16); 46 | string_nprintf(&s, 0, "Hello"); 47 | string_nprintf(&s, 0, " world"); 48 | s.raw_size--; 49 | buffer_putchar(&s, '!'); 50 | buffer_putchar(&s, '\n'); 51 | buffer_putchar(&s, '\0'); 52 | assert(strncmp(s.data, "Hello world!\n", s.raw_size) == 0); 53 | assert(s.raw_size == 14 && s.capacity == 32); 54 | string_nprintf(&s, 0, "Bye bye world!"); 55 | string_nprintf(&s, 0, "%d", 10); 56 | assert(strncmp(s.data, "Hello world!\nBye bye world!10", s.raw_size) == 0); 57 | buffer_free(&s); 58 | 59 | s = mk_string(16); 60 | const char *long_str = "Long string..............................."; 61 | string_nprintf(&s, 0, "%s", long_str); 62 | assert(strncmp(s.data, long_str, s.raw_size) == 0); 63 | buffer_free(&s); 64 | 65 | s = mk_string(16); 66 | string_nprintf(&s, 11, "%s", long_str); 67 | assert(strncmp(s.data, "Long strin", s.raw_size) == 0); 68 | buffer_free(&s); 69 | 70 | json_stack stack; 71 | stack.size = 0; 72 | stack_push(&stack, (json_pos){mk_string_value("val1"), 1}); 73 | stack_push(&stack, (json_pos){mk_string_value("val2"), 2}); 74 | assert(stack.size == 2); 75 | assert(stack_peek(&stack)->index == 2); 76 | assert(stack_peekn(&stack, 1)->index == 1); 77 | stack_pop(&stack); 78 | assert(stack_peek(&stack)->index == 1); 79 | } 80 | 81 | int main() { 82 | #ifdef DEBUG 83 | trace = fopen("trace.txt", "w"); 84 | #endif 85 | 86 | parse_round_trip_test(stdin); 87 | data_struct_test(); 88 | 89 | #ifdef DEBUG 90 | fclose(trace); 91 | #endif 92 | } 93 | -------------------------------------------------------------------------------- /src/theme.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "theme.h" 4 | 5 | #define ROW_SEL_BG FMT_BG_RED 6 | #define ROW_SEL_FG FMT_FG_WHITE 7 | -------------------------------------------------------------------------------- /src/trace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "stdio.h" 4 | 5 | #ifdef DEBUG 6 | extern FILE *trace; 7 | 8 | #define TRACE(...) fprintf(trace, __VA_ARGS__) 9 | 10 | #else 11 | #define TRACE(...) {} 12 | #endif 13 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | static inline int min(int a, int b) { 4 | return a < b ? a : b; 5 | } 6 | 7 | static inline int max(int a, int b) { 8 | return a > b ? a : b; 9 | } --------------------------------------------------------------------------------