From 900e841228331832ec49b2f0514182bcb39fa73e Mon Sep 17 00:00:00 2001 From: jacob Date: Mon, 1 Apr 2024 15:09:14 -0500 Subject: [PATCH] rewrite json parser to be dumber --- CMakeLists.txt | 8 +- src/app.c | 102 ++- src/arena.h | 17 +- src/game.c | 3 +- src/json.c | 1803 +++++++++++++++++++++--------------------------- src/json.h | 146 +--- src/settings.c | 262 ++++--- src/settings.h | 7 +- 8 files changed, 1056 insertions(+), 1292 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5000f800..6f31e148 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,6 +145,7 @@ target_precompile_headers(powerplay_exe PRIVATE src/common.h) set(COMPILER_FLAGS " -fno-strict-aliasing \ -fno-finite-loops \ +-fwrapv \ -msse4.2 \ ") @@ -175,9 +176,10 @@ if (RTC) message(FATAL_ERROR "CRTLIB (C runtime library) Must be enabled when compiling with RTC (runtime checks)") endif() # NOTE: Adress sanitizer is disabled for now because for some reason it's screwing up viewing local variables while debugging. - # set(COMPILER_FLAGS "${COMPILER_FLAGS} -DRTC=1") - # set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=undefined -fno-sanitize=alignment -DRTC=1") - set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=undefined -fsanitize-trap=undefined -fno-sanitize=alignment -DRTC=1") + set(COMPILER_FLAGS "${COMPILER_FLAGS} -DRTC=1") + set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=undefined -fsanitize-trap=undefined -DRTC=1") + # set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=undefined -DRTC=1") + # set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=address -fsanitize=undefined -DRTC=1") # set(COMPILER_FLAGS "${COMPILER_FLAGS} -fsanitize=address -fsanitize=undefined -fno-sanitize=alignment -DRTC=1") endif() diff --git a/src/app.c b/src/app.c index bcff3943..4288c506 100644 --- a/src/app.c +++ b/src/app.c @@ -20,8 +20,24 @@ #include "util.h" #include "settings.h" #include "draw.h" +#include "math.h" #define WRITE_DIR "power_play" +#define SETTINGS_FILENAME "settings.json" + +#if RTC +# if DEVELOPER +# define WINDOW_TITLE "Debug (Developer Build)" +# else +# define WINDOW_TITLE "Debug" +# endif +#else +# if DEVELOPER +# define WINDOW_TITLE "Power Play (Developer Build)" +# else +# define WINDOW_TITLE "Power Play" +# endif +#endif GLOBAL struct { struct arena arena; @@ -63,6 +79,30 @@ struct string app_write_path_cat(struct arena *arena, struct string filename) return string_cat(arena, L.write_path, filename); } +/* ========================== * + * Default settings + * ========================== */ + +INTERNAL struct sys_window_settings default_window_settings(struct sys_window *window) +{ + __prof; + + struct v2 monitor_size = sys_window_get_monitor_size(window); + + i32 width = 1280; + i32 height = math_round(width / (f32)(DEFAULT_CAMERA_WIDTH / DEFAULT_CAMERA_HEIGHT)); + i32 x = math_round(monitor_size.x / 2.f - width / 2); + i32 y = math_round(monitor_size.y / 2.f - height / 2); + + return (struct sys_window_settings) { + .title = WINDOW_TITLE, + .floating_x = x, + .floating_y = y, + .floating_width = width, + .floating_height = height + }; +} + /* ========================== * * Entry point * ========================== */ @@ -91,9 +131,8 @@ void app_entry_point(void) L.arena = arena_alloc(GIGABYTE(64)); L.write_path = initialize_write_directory(&L.arena, STR(WRITE_DIR)); + /* Startup base systems */ { - /* Startup base systems */ - /* Startup logging */ { struct temp_arena scratch = scratch_begin_no_conflict(); @@ -111,11 +150,44 @@ void app_entry_point(void) /* Create window */ struct sys_window window = sys_window_alloc(); + + /* Read window settings from file */ { - /* Read window settings from file */ - struct sys_window_settings window_settings = settings_default_window_settings(&window); - settings_read_from_file(&window_settings); + struct temp_arena scratch = scratch_begin_no_conflict(); + + struct sys_window_settings window_settings = default_window_settings(&window); + struct string settings_path = app_write_path_cat(scratch.arena, STR(SETTINGS_FILENAME)); + logf_info("Looking for settings file \"%F\"", FMT_STR(settings_path)); + if (sys_is_file(settings_path)) { + logf_info("Settings file found"); + struct sys_file settings_file = sys_file_open_read(settings_path); + struct buffer file_data = sys_file_read_all(scratch.arena, settings_file); + sys_file_close(settings_file); + logf_info("Deserializing settings file data: %F", FMT_STR(STRING_FROM_BUFFER(file_data))); + struct string error = { 0 }; + struct sys_window_settings *res = settings_deserialize(scratch.arena, file_data, &error); + if (error.len > 0) { + logf_info("Failed to load settings file with error - %F", FMT_STR(error)); + struct string msg = string_format(scratch.arena, + STR( + "Failed to loading settings file \"%F\":\n" + "------------\n" + "%F\n" + "------------\n" + "To stop this error from appearing, either Fix the issue above or delete the file from the system." + ), + FMT_STR(settings_path), + FMT_STR(error)); + sys_panic(msg); + } else { + logf_info("Settings file loaded successfully"); + window_settings = *res; + } + } + string_copy_buff(BUFFER_FROM_ARRAY(window_settings.title), STR(WINDOW_TITLE)); sys_window_update_settings(&window, &window_settings); + + scratch_end(scratch); } /* Startup systems */ @@ -153,8 +225,24 @@ void app_entry_point(void) work_shutdown(); /* Write window settings to file */ - struct sys_window_settings settings = sys_window_get_settings(&window); - settings_write_to_file(&settings); + { + struct temp_arena scratch = scratch_begin_no_conflict(); + + struct string window_settings_path = app_write_path_cat(scratch.arena, STR(SETTINGS_FILENAME)); + + struct sys_window_settings settings = sys_window_get_settings(&window); + struct buffer buff = settings_serialize(scratch.arena, &settings); + logf_info("Serialized window settings: %F", FMT_STR(STRING_FROM_BUFFER(buff))); + + logf_info("Writing settings file to path \"%F\"", FMT_STR(window_settings_path)); + struct sys_file settings_file = sys_file_open_write(window_settings_path); + sys_file_write(settings_file, buff); + sys_file_close(settings_file); + logf_info("Finished writing settings file"); + + scratch_end(scratch); + } + logf_info("Program exited normally"); } diff --git a/src/arena.h b/src/arena.h index b85dc9d9..28408670 100644 --- a/src/arena.h +++ b/src/arena.h @@ -9,8 +9,8 @@ #define arena_push_array(a, type, n) ((type *)_arena_push_bytes((a), (sizeof(type) * (n)), ALIGNOF(type))) #define arena_push_array_zero(a, type, n) ((type *)_arena_push_bytes_zero((a), (sizeof(type) * (n)), ALIGNOF(type))) -#define arena_pop(a, type) ((type *)_arena_pop((a), sizeof(type))) -#define arena_pop_array(a, type, n) ((type *)_arena_pop((a), sizeof(type) * (n))) +#define arena_pop(a, type, dest) _arena_pop_struct((a), sizeof(type), dest) +#define arena_pop_array(a, type, n, dest) _arena_pop_struct((a), sizeof(type) * (n), dest) /* Returns a pointer to where the next allocation would be (at alignment of type). * Equivalent arena_push but without actually allocating anything. */ @@ -40,18 +40,23 @@ INLINE void *_arena_push_bytes_zero(struct arena *arena, u64 size, u64 align) return p; } -INLINE void *_arena_pop_to(struct arena *arena, u64 pos) +INLINE void _arena_pop_to(struct arena *arena, u64 pos) { ASSERT(arena->pos >= pos); ASAN_POISON(arena->base + pos, arena->pos - pos); arena->pos = pos; - return (void *)(arena->base + arena->pos); } -INLINE void *_arena_pop(struct arena *arena, u64 size) +INLINE void _arena_pop_struct(struct arena *arena, u64 size, void *copy_dest) { ASSERT(arena->pos >= size); - return _arena_pop_to(arena, arena->pos - size); + + u64 new_pos = arena->pos - size; + void *src = (void *)(arena->base + new_pos); + MEMCPY(copy_dest, src, size); + + ASAN_POISON(arena->base + new_pos, arena->pos - new_pos); + arena->pos = new_pos; } INLINE struct temp_arena arena_temp_begin(struct arena *arena) diff --git a/src/game.c b/src/game.c index 80608ee9..254a81b4 100644 --- a/src/game.c +++ b/src/game.c @@ -86,7 +86,8 @@ INTERNAL void recalculate_world_xform_recurse(struct entity *parent) u64 stack_count = 1; while (stack_count > 0) { /* Pull from top of stack */ - struct stack_node node = *arena_pop(scratch.arena, struct stack_node); + struct stack_node node; + arena_pop(scratch.arena, struct stack_node, &node); --stack_count; /* Calculate child world xform */ diff --git a/src/json.c b/src/json.c index 7f3e86cb..55efd910 100644 --- a/src/json.c +++ b/src/json.c @@ -1,1083 +1,848 @@ #include "json.h" #include "string.h" #include "arena.h" -#include "memory.h" #include "scratch.h" #include "math.h" -#include "util.h" -#define OBJECT_HASH_TABLE_CAPACITY_FACTOR 1.5 +/* Non-standard-conforming JSON parser. + * - Unicode not supported (TODO) + * - Unicode escape sequences in strings (\u) not supported + * - Leading 0s in numbers are allowed + */ -#define MAX_CHILDREN (U32_MAX - 1) +/* ========================== * + * Lex + * ========================== */ #define CASE_NEWLINE \ case 0x0A: /* Line feed or New line */ \ case 0x0D /* Carriage return */ -#define CASE_WHITESPACE \ - CASE_NEWLINE: \ +#define CASE_SPACE \ case 0x20: /* Space */ \ case 0x09 /* Horizontal tab */ -#define CASE_NUMERIC \ - case '0': \ - case '1': \ - case '2': \ - case '3': \ - case '4': \ - case '5': \ - case '6': \ - case '7': \ - case '8': \ - case '9' +#define CASE_ALPHABETICAL \ + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': \ + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z' -#define CASE_ALPHABETICAL_LOWERCASE \ - case 'a': \ - case 'b': \ - case 'c': \ - case 'd': \ - case 'e': \ - case 'f': \ - case 'g': \ - case 'h': \ - case 'i': \ - case 'j': \ - case 'k': \ - case 'l': \ - case 'm': \ - case 'n': \ - case 'o': \ - case 'p': \ - case 'q': \ - case 'r': \ - case 's': \ - case 't': \ - case 'u': \ - case 'v': \ - case 'w': \ - case 'x': \ - case 'y': \ - case 'z' +#define CASE_DIGIT \ + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9' + +#define CASE_SYMBOL \ + case ',': case ':': case '[': case ']': case '{': case '}' + +enum token_type { + TOKEN_TYPE_UNKNOWN, + + TOKEN_TYPE_NUMBER, + TOKEN_TYPE_STRING, + TOKEN_TYPE_TRUE, + TOKEN_TYPE_FALSE, + TOKEN_TYPE_NULL, + + TOKEN_TYPE_COMMA, + TOKEN_TYPE_COLON, + TOKEN_TYPE_SQUARE_BRACE_OPEN, + TOKEN_TYPE_SQUARE_BRACE_CLOSE, + TOKEN_TYPE_CURLY_BRACE_OPEN, + TOKEN_TYPE_CURLY_BRACE_CLOSE, + + TOKEN_TYPE_EOF +}; + +struct token { + enum token_type type; + u64 start; + u64 end; + struct token *next; +}; + +struct lex_result { + struct token *token_first; + struct token *token_last; +}; + +enum lex_number_state { + LEX_NUMBER_STATE_WHOLE, + LEX_NUMBER_STATE_FRACTION, + LEX_NUMBER_STATE_EXPONENT +}; + +GLOBAL READONLY struct string g_keyword_strings[] = { + ['t'] = STR("true"), + ['f'] = STR("false"), + ['n'] = STR("null") +}; + +GLOBAL READONLY enum token_type g_keyword_types[] = { + ['t'] = TOKEN_TYPE_TRUE, + ['f'] = TOKEN_TYPE_FALSE, + ['n'] = TOKEN_TYPE_NULL +}; + +INTERNAL struct lex_result lex(struct arena *arena, struct string src) +{ + struct lex_result res = { 0 }; + + u64 pos = 0; + b32 lexing_done = false; + while (!lexing_done) { + /* Skip whitespace */ + b32 whitespace_done = false; + while (!whitespace_done && pos < src.len) { + switch (src.text[pos]) { + CASE_NEWLINE: + CASE_SPACE: { + ++pos; + } break; + + default: { + whitespace_done = true; + } break; + } + } + + /* Create token */ + struct token *t = arena_push_zero(arena, struct token); + t->start = pos; + + /* Push token to list */ + if (!res.token_first) { + res.token_first = t; + } else { + res.token_last->next = t; + } + res.token_last = t; + + if (pos >= src.len) { + t->type = TOKEN_TYPE_EOF; + lexing_done = true; + } else { + /* Lex known token types */ + switch (src.text[pos]) { + /* Symbols */ + case ',': { + t->type = TOKEN_TYPE_COMMA; + ++pos; + } break; + + case ':': { + t->type = TOKEN_TYPE_COLON; + ++pos; + } break; + + case '[': { + t->type = TOKEN_TYPE_SQUARE_BRACE_OPEN; + ++pos; + } break; + + case ']': { + t->type = TOKEN_TYPE_SQUARE_BRACE_CLOSE; + ++pos; + } break; + + case '{': { + t->type = TOKEN_TYPE_CURLY_BRACE_OPEN; + ++pos; + } break; + + case '}': { + t->type = TOKEN_TYPE_CURLY_BRACE_CLOSE; + ++pos; + } break; + + /* Number */ + case '-': { + /* Verify '-' precedes digit */ + b32 next_is_digit = false; + if ((pos + 1) < src.len) { + switch (src.text[pos + 1]) { + CASE_DIGIT: { + next_is_digit = true; + } break; + } + } + ++pos; + if (!next_is_digit) { + break; + } + } FALLTHROUGH; + CASE_DIGIT: { + t->type = TOKEN_TYPE_NUMBER; + enum lex_number_state state = LEX_NUMBER_STATE_WHOLE; + b32 number_done = false; + while (!number_done && pos < src.len) { + switch (src.text[pos]) { + CASE_DIGIT: { + ++pos; + } break; + + case '.': { + u64 consume = 0; + if (state == LEX_NUMBER_STATE_WHOLE && (pos + 1) < src.len) { + u8 c1 = src.text[pos + 1]; + switch (c1) { + CASE_DIGIT: { + /* Consume '.' */ + ++consume; + } break; + + default: break; + } + } + if (consume) { + state = LEX_NUMBER_STATE_FRACTION; + pos += consume; + } else { + number_done = true; + } + } break; + + case 'e': + case 'E': { + u64 consume = 0; + if ((state == LEX_NUMBER_STATE_WHOLE || state == LEX_NUMBER_STATE_FRACTION) && (pos + 1) < src.len) { + u8 c1 = src.text[pos + 1]; + switch (c1) { + CASE_DIGIT: { + /* Consume 'E'/'e' */ + ++consume; + } break; + + case '-': + case '+': { + if ((pos + 2) < src.len) { + u8 c2 = src.text[pos + 2]; + switch (c2) { + CASE_DIGIT: { + /* Consume 'E'/'e' & '+'/'-' */ + consume += 2; + } break; + + default: break; + } + } + } break; + + default: break; + } + } + if (consume) { + state = LEX_NUMBER_STATE_EXPONENT; + pos += consume; + } else { + number_done = true; + } + } break; + + default: { + number_done = true; + } break; + } + } + } break; + + /* String */ + case '"': { + ++pos; + + b32 string_done = false; + b32 next_escaped = false; + while (!string_done && pos < src.len) { + b32 escaped = next_escaped; + next_escaped = false; + switch (src.text[pos]) { + CASE_NEWLINE: { + ++pos; + string_done = true; + } break; + + case '"': { + ++pos; + if (!escaped) { + t->type = TOKEN_TYPE_STRING; + string_done = true; + } + } break; + + case '\\': { + ++pos; + if (!escaped) { + next_escaped = true; + } + } break; + + default: { + ++pos; + } break; + } + } + } break; + + /* Keywords */ + case 't': + case 'f': + case 'n': { + struct string keyword = g_keyword_strings[src.text[pos]]; + + b32 match = true; + /* FIXME: Verify bounds checks are correct here */ + if ((pos + keyword.len - 1) < src.len) { + if ((pos + keyword.len) < src.len) { + /* Don't match if word continues past keyword */ + switch (src.text[pos + keyword.len]) { + CASE_SYMBOL: + CASE_SPACE: + CASE_NEWLINE: { + } break; + + default: { + match = false; + } break; + } + } + if (match) { + struct string cmp_str = { + .len = keyword.len, + .text = &src.text[pos] + }; + match = string_eq(cmp_str, keyword); + } + } + + if (match) { + t->type = g_keyword_types[src.text[pos]]; + pos += keyword.len; + } + } break; + + default: break; + } + } + + /* Lex unknown token */ + if (t->type == TOKEN_TYPE_UNKNOWN) { + b32 unknown_done = false; + while (!unknown_done && pos < src.len) { + switch (src.text[pos]) { + CASE_SYMBOL: + CASE_SPACE: + CASE_NEWLINE: { + unknown_done = true; + } break; + + default: { + ++pos; + } break; + } + } + t->end = pos; + + /* Exit early if unknown token encountered */ + return res; + } else { + t->end = pos; + } + } + + return res; +} + +/* ========================== * + * Interpret + * ========================== */ + +INTERNAL void append_char(struct arena *arena, struct string *str, u8 c) +{ + *arena_push(arena, u8) = c; + ++str->len; +} + +INTERNAL f64 interpret_number(struct string src) +{ + b32 whole_present = false; + u64 whole_left = 0; + u64 whole_right = 0; + i32 whole_sign = 1; + + b32 fraction_present = false; + u64 fraction_left = 0; + u64 fraction_right = 0; + + b32 exponent_present = false; + u64 exponent_left = 0; + u64 exponent_right = 0; + i32 exponent_sign = 1; + (UNUSED)exponent_present; + (UNUSED)exponent_left; + (UNUSED)exponent_right; + (UNUSED)exponent_sign; + + /* Lex number parts */ + { + u64 pos = 0; + if (src.len > 0 && src.text[0] == '-') { + whole_sign = -1; + ++pos; + } + + enum lex_number_state state = LEX_NUMBER_STATE_WHOLE; + while (pos < src.len) { + switch (src.text[pos]) { + CASE_DIGIT: { + switch (state) { + case LEX_NUMBER_STATE_WHOLE: { + if (!whole_present) { + whole_present = true; + whole_left = pos; + } + whole_right = pos; + ++pos; + } break; + + case LEX_NUMBER_STATE_FRACTION: { + if (!fraction_present) { + fraction_present = true; + fraction_left = pos; + } + fraction_right = pos; + ++pos; + } break; + + case LEX_NUMBER_STATE_EXPONENT: { + if (!exponent_present) { + exponent_present = true; + exponent_left = pos; + } + exponent_right = pos; + ++pos; + } break; + } + } break; + + case '.': { + state = LEX_NUMBER_STATE_FRACTION; + ++pos; + } break; + + case 'e': + case 'E': { + state = LEX_NUMBER_STATE_EXPONENT; + ++pos; + } break; + + case '-': { + switch (state) { + case LEX_NUMBER_STATE_WHOLE: { + whole_sign = -1; + ++pos; + } break; + + case LEX_NUMBER_STATE_EXPONENT: { + exponent_sign = -1; + ++pos; + } break; + + default: { + /* Unreachable */ + ASSERT(false); + ++pos; + } break; + } + } break; + + case '+': { + switch (state) { + case LEX_NUMBER_STATE_EXPONENT: { + exponent_sign = 1; + ++pos; + } break; + + default: { + /* Unreachable */ + ASSERT(false); + ++pos; + } break; + } + } break; + + default: { + /* Unreachable */ + ASSERT(false); + ++pos; + } break; + } + } + } + + f64 res = 0; + + /* Process whole part */ + if (whole_present) { + u64 pos = whole_left; + while (pos <= whole_right) { + u8 digit = min_u8(src.text[pos] - 48, 9); + u64 exp = whole_right - pos; + res += digit * math_pow_u64(10, exp); + ++pos; + } + res *= whole_sign; + } + + /* Process fraction part */ + if (fraction_present) { + u64 frac_whole = 0; + u64 pos = fraction_left; + while (pos <= fraction_right) { + u8 digit = min_u8(src.text[pos] - 48, 9); + u64 exp = fraction_right - pos; + frac_whole += digit * math_pow_u64(10, exp); + ++pos; + } + + res += (f64)frac_whole / math_pow_u64(10, (fraction_right - fraction_left + 1)); + } + + /* Process exponent part */ + if (exponent_present) { + u64 exponent_whole = 0; + u64 pos = exponent_left; + while (pos <= exponent_right) { + u8 digit = min_u8(src.text[pos] - 48, 9); + u64 exp = exponent_right - pos; + exponent_whole += digit * math_pow_u64(10, exp); + ++pos; + } + + if (exponent_sign >= 0) { + res *= math_pow_u64(10, exponent_whole); + } else { + res /= math_pow_u64(10, exponent_whole); + } + } + + return res; +} + +INTERNAL struct string interpret_string(struct arena *arena, struct string src, struct string *error) +{ + (UNUSED)arena; + (UNUSED)src; + (UNUSED)error; + + struct string res = { + .len = 0, + .text = arena_dry_push(arena, u8) + }; + + if (src.len < 2) { + if (error) { + *error = STR("Malformed string."); + } + return res; + } + /* Ignore beginning quote */ + u64 pos = 1; + + b32 valid_close = false; + b32 string_done = false; + b32 next_escaped = false; + while (!string_done && pos < src.len) { + b32 escaped = next_escaped; + next_escaped = false; + + if (escaped) { + switch (src.text[pos]) { + case '"': + case '\\': + case '/': { + append_char(arena, &res, src.text[pos]); + ++pos; + } break; + + /* Backspace */ + case 'b': { + append_char(arena, &res, '\b'); + ++pos; + } break; + + /* Formfeed */ + case 'f': { + append_char(arena, &res, '\f'); + ++pos; + } break; + + /* Linefeed */ + case 'n': { + append_char(arena, &res, '\n'); + ++pos; + } break; + + /* Carriage return */ + case 'r': { + append_char(arena, &res, '\r'); + ++pos; + } break; + + /* Horizontal tab */ + case 't': { + append_char(arena, &res, '\t'); + ++pos; + } break; + + /* TODO: Unicode escape support */ +#if 0 + case 'u': { + /* TODO */ + } break; +#endif + + default: { + if (error) { + *error = STR("Invalid escape character in string."); + return res; + } + } break; + } + } else { + switch (src.text[pos]) { + case '\\': { + escaped = true; + ++pos; + } break; + + case '"': { + string_done = true; + valid_close = true; + ++pos; + } break; + + default: { + append_char(arena, &res, src.text[pos]); + ++pos; + } break; + } + } + } + + if (!valid_close) { + if (error) { + *error = STR("Expected end of string."); + } + } + + return res; +} /* ========================== * * Parse * ========================== */ struct parser { - u8 *at; - u8 *end; - struct string error; + struct string src; + struct token *t; + struct json_error_list errors; }; -INTERNAL struct json_ir parse_json(struct arena *arena, struct parser *p, b32 parse_key); - -INTERNAL void set_error_unexpected_character(struct arena *arena, struct parser *p) +INTERNAL void push_error(struct arena *arena, struct parser *p, struct token *t, struct string msg) { - __prof; - p->error = string_format(arena, STR("Unexpected character '%F'"), FMT_CHAR(*p->at)); -} - -INTERNAL b32 is_error(struct parser *p) -{ - __prof; - return p->error.len > 0; -} - -INTERNAL f64 parse_number(struct arena *arena, struct parser *p) -{ - __prof; - /* Find start */ - u8 *start = p->at; - b32 found_start = false; - while (!found_start && p->at < p->end) { - switch (*p->at) { - CASE_WHITESPACE: { - ++p->at; - } break; - - case '-': - case '.': - CASE_NUMERIC: { - start = p->at; - found_start = true; - } break; - - default: { - set_error_unexpected_character(arena, p); - return 0; - } break; - } - } - - /* Find end, decimal, and sign */ - u8 *end = p->at; - b32 found_end = false; - u8 *dec_at = NULL; - i8 sign = 1; - while (!found_end && p->at < p->end) { - switch (*p->at) { - case '.': { - if (dec_at) { - set_error_unexpected_character(arena, p); - return 0; - } else { - dec_at = p->at; - ++p->at; - } - } break; - - case '-': { - if (p->at == start) { - sign = -1; - ++p->at; - ++start; - } else { - set_error_unexpected_character(arena, p); - return 0; - } - } break; - - CASE_NUMERIC: { - ++p->at; - } break; - - default: { - end = p->at; - found_end = true; - } break; - } - } - - if (!dec_at) { - dec_at = end; - } - - f64 sum = 0; - - /* Accum whole num */ - u8 *whole_at = dec_at - 1; - while (whole_at >= start) { - /* TODO: validate digit */ - u64 digit = (u64)*whole_at - 48; - sum += digit * math_pow_u64(10, (u8)(dec_at - whole_at - 1)); - --whole_at; - } - - /* Accum frac num */ - u8 *frac_at = dec_at + 1; - f64 pow = (f64)math_pow_u64(10, (u8)(frac_at - dec_at)); - while (frac_at < end) { - /* TODO: validate digit */ - u64 digit = (u64)*frac_at - 48; - sum += (f64)digit / pow; - ++frac_at; - pow /= 10; - } - - //sum += (f64)frac_sum / (f64)pow_u(10, end - dec_at); - return sum * sign; -} - -INTERNAL void append_char(struct arena *arena, struct string *str, u8 c) -{ - __prof; - u8 *c_ptr = arena_push(arena, u8); - *c_ptr = c; - ++str->len; -} - -INTERNAL struct string parse_string(struct arena *arena, struct parser *p) -{ - __prof; - struct string str = { - .len = 0, - .text = arena_dry_push(arena, u8) + struct json_error *error = arena_push(arena, struct json_error); + *error = (struct json_error) { + .msg = msg, + .start = t->start, + .end = t->end }; - - /* Find '"' string start */ - u8 *start = NULL; - while (!start && p->at < p->end) { - switch (*p->at) { - CASE_WHITESPACE: { - ++p->at; - } break; - - case '"': { - ++p->at; - start = p->at; - } break; - - default: { - set_error_unexpected_character(arena, p); - return str; - } break; - } + struct json_error_list *list = &p->errors; + if (!list->first) { + list->first = error; + } else { + list->last->next = error; } + list->last = error; + ++list->count; +} - if (!start) { - p->error = STR("Reached end of file without finding a string"); - return str; - } +INTERNAL struct json *parse(struct arena *arena, struct parser *p) +{ + struct temp_arena scratch = scratch_begin(arena); - /* Parse characters */ - while (p->at < p->end) { - if (*p->at == '\\') { - /* Escaped */ - if ((p->at + 1) < p->end) { - ++p->at; - switch (*p->at) { - CASE_NEWLINE: { - p->error = STR("Expected '\"' to close string"); - return str; - } break; + struct string src = p->src; + struct json *res = arena_push_zero(arena, struct json); - /* Backspace */ - case 'b': { - append_char(arena, &str, '\b'); - } break; + /* Depth first stack */ + *arena_push(scratch.arena, struct json *) = res; + u64 stack_count = 1; - /* Form feed */ - case 'f': { - append_char(arena, &str, '\f'); - } break; - - /* Newline */ - case 'n': { - append_char(arena, &str, '\n'); - } break; - - /* Carriage return */ - case 'r': { - append_char(arena, &str, '\r'); - } break; - - /* Tab*/ - case 't': { - append_char(arena, &str, '\t'); - } break; - - case '"': /* Double quote */ - case '\\': { /* Backslash */ - append_char(arena, &str, *p->at); - } break; - - default: { - p->error = string_format( - arena, - STR("Unknown escape sequence '\\%F'"), - FMT_CHAR(*p->at) - ); - return str; - } break; - } - ++p->at; + while (stack_count > 0) { + struct json *json = NULL; + arena_pop(scratch.arena, struct json *, &json); + --stack_count; + struct json *parent_json = json->parent; + b32 is_new_parent = false; + if (json->type == JSON_TYPE_OBJECT || json->type == JSON_TYPE_ARRAY) { + /* No more children to parse for object/array, check for closing brace. */ + enum token_type tok_close_type = json->type == JSON_TYPE_OBJECT ? TOKEN_TYPE_CURLY_BRACE_CLOSE : TOKEN_TYPE_SQUARE_BRACE_CLOSE; + if (p->t->type == tok_close_type) { + p->t = p->t->next; } else { - set_error_unexpected_character(arena, p); - return str; + push_error(arena, p, p->t, STR("Expected comma.")); + p->t = p->t->next; + goto abort; } } else { - switch (*p->at) { - CASE_NEWLINE: { - p->error = STR("Expected '\"' to close string"); - return str; - } break; - - /* End string */ - case '"': { - ++p->at; - return str; - } break; - - /* Append character */ - default: { - append_char(arena, &str, *p->at); - ++p->at; - } break; - } - } - } - - p->error = STR("Expected '\"' to close string"); - return str; -} - -/* Parses booleans & null. - * Returns boolean value (if value type is boolean). Returns false if null value type. */ -INTERNAL b32 parse_primitive(struct arena *arena, struct parser *p, enum json_type *type) -{ - __prof; - (UNUSED)arena; - - /* Find start */ - u8 *start = p->at; - b32 found_start = false; - while (!found_start && p->at < p->end) { - switch (*p->at) { - CASE_WHITESPACE: { - ++p->at; - } break; - - default: { - start = p->at; - found_start = true; - }; - } - } - - /* Find end */ - u8 *end = p->at; - b32 found_end = false; - while (!found_end && p->at < p->end) { - switch (*p->at) { - CASE_ALPHABETICAL_LOWERCASE: { - ++p->at; - } break; - - default: { - end = p->at; - found_end = true; - }; - } - } - - struct string val_str = { - .len = end - start, - .text = start - }; - - if (string_eq(val_str, STR("null"))) { - *type = JSON_TYPE_NULL; - return false; - } else if (string_eq(val_str, STR("true"))) { - *type = JSON_TYPE_BOOL; - return true; - } else if (string_eq(val_str, STR("false"))) { - *type = JSON_TYPE_BOOL; - return false; - } else { - p->error = STR("Expected value"); - return false; - } -} - -INTERNAL struct json_ir_parent_data parse_array(struct arena *arena, struct parser *p) -{ - __prof; - struct json_ir_parent_data data = { 0 }; - - /* Find '[' array start */ - b32 found_start = false; - while (!found_start && p->at < p->end) { - switch (*p->at) { - CASE_WHITESPACE: { - ++p->at; - } break; - - case '[': { - ++p->at; - found_start = true; - } break; - - default: { - set_error_unexpected_character(arena, p); - return data; - } break; - } - } - - if (!found_start) { - set_error_unexpected_character(arena, p); - return data; - } - - /* Parse array */ - b32 preceding_comma = false; - while (p->at < p->end) { - switch (*p->at) { - CASE_WHITESPACE: { - ++p->at; - } break; - - /* End array */ - case ']': { - if (preceding_comma) { - p->error = STR("Trailing comma"); - return data; - } - ++p->at; - return data; - } break; - - /* Parse value */ - case ',': { - if (data.child_first) { - ++p->at; - preceding_comma = true; - } else { - set_error_unexpected_character(arena, p); - return data; - } - } break; - - default: { - if (!preceding_comma && data.child_count > 0) { - p->error = STR("Expected comma"); - return data; - } - if (data.child_count >= MAX_CHILDREN) { - p->error = STR("Max children reached"); - return data; - } - struct json_ir *child = arena_push(arena, struct json_ir); - *child = parse_json(arena, p, false); - if (is_error(p)) { - return data; - } - if (data.child_last) { - data.child_last->next_child = child; - } else if (!data.child_first) { - data.child_first = child; - } - data.child_last = child; - ++data.child_count; - preceding_comma = false; - } break; - } - } - - p->error = STR("Reached end of file without closing object (no '}' found)"); - return data; -} - -INTERNAL struct json_ir_parent_data parse_object(struct arena *arena, struct parser *p) -{ - __prof; - struct json_ir_parent_data data = { 0 }; - - /* Find '{' object start */ - b32 found_start = false; - while (!found_start && p->at < p->end) { - switch (*p->at) { - CASE_WHITESPACE: { - ++p->at; - } break; - - case '{': { - ++p->at; - found_start = true; - } break; - - default: { - set_error_unexpected_character(arena, p); - return data; - } break; - } - } - - if (!found_start) { - set_error_unexpected_character(arena, p); - return data; - } - - /* Parse object */ - b32 preceding_comma = false; - while (p->at < p->end) { - switch (*p->at) { - CASE_WHITESPACE: { - ++p->at; - } break; - - /* End object */ - case '}': { - if (preceding_comma) { - p->error = STR("Trailing comma"); - return data; - } - ++p->at; - return data; - } break; - - /* Parse value */ - case ',': { - if (data.child_first) { - ++p->at; - preceding_comma = true; - } else { - set_error_unexpected_character(arena, p); - return data; - } - } break; - - case '"': { - if (!preceding_comma && data.child_count > 0) { - p->error = STR("Expected comma"); - return data; - } - if (data.child_count >= MAX_CHILDREN) { - p->error = STR("Max children reached"); - return data; - } - struct json_ir *child = arena_push(arena, struct json_ir); - *child = parse_json(arena, p, true); - if (is_error(p)) { - return data; - } - if (data.child_last) { - data.child_last->next_child = child; - } else if (!data.child_first) { - data.child_first = child; - } - data.child_last = child; - ++data.child_count; - preceding_comma = false; - } break; - - default: { - set_error_unexpected_character(arena, p); - return data; - } break; - } - } - - p->error = STR("Reached end of file without closing object (no '}' found)"); - return data; -} - -INTERNAL void parse_whitespace_until_end(struct arena *arena, struct parser *p) -{ - __prof; - while (p->at < p->end) { - switch (*p->at) { - CASE_WHITESPACE: { - ++p->at; - } break; - - default: { - set_error_unexpected_character(arena, p); - return; - } break; - } - } -} - -INTERNAL struct json_ir parse_json(struct arena *arena, struct parser *p, b32 parse_key) -{ - __prof; - struct json_ir ir = { 0 }; - - /* Parse key (if necessary) */ - if (parse_key) { - /* Parse key string */ - ir.key = parse_string(arena, p); - if (is_error(p)) { - return ir; - } - - /* Eat ':' */ - b32 found_colon = false; - while (!found_colon && p->at < p->end) { - switch (*p->at) { - CASE_WHITESPACE: { - ++p->at; - } break; - - case ':': { - ++p->at; - found_colon = true; - } break; - - default: { - p->error = STR("Colon expected"); - return ir; - } break; - } - } - - if (!found_colon) { - p->error = STR("Colon expected"); - return ir; - } - } - - /* Eat whitespace until value start */ - b32 value_start = false; - while (!value_start && p->at < p->end) { - switch (*p->at) { - CASE_WHITESPACE: { - ++p->at; - } break; - - default: { - value_start = true; - } break; - } - } - - /* Parse value */ - switch (*p->at) { - case '"': { - ir.type = JSON_TYPE_STRING; - ir.val.string = parse_string(arena, p); - } break; - - case '-': - case '.': - CASE_NUMERIC: { - ir.type = JSON_TYPE_NUMBER; - ir.val.number = parse_number(arena, p); - } break; - - case '{': { - ir.type = JSON_TYPE_OBJECT; - ir.val.children = parse_object(arena, p); - } break; - - case '[': { - ir.type = JSON_TYPE_ARRAY; - ir.val.children = parse_array(arena, p); - } break; - - default: { - ir.val.boolean = parse_primitive(arena, p, &ir.type); - } break; - } - - return ir; -} - -const struct json_ir *json_parse(struct arena *arena, struct buffer bytes, struct string **error) -{ - __prof; - struct parser p = { - .at = bytes.data, - .end = bytes.data + bytes.size - }; - - struct json_ir *root_ir = arena_push(arena, struct json_ir); - *root_ir = parse_json(arena, &p, false); - if (!is_error(&p)) { - /* Ensure that there is no data other than root object / value in buffer */ - parse_whitespace_until_end(arena, &p); - } - - if (is_error(&p)) { - /* Handle error */ - if (error) { - struct string err_msg = p.error.len > 0 ? p.error : STR("Parsing error"); - *error = arena_push(arena, struct string); - **error = string_copy(arena, err_msg); - } - } - - return root_ir; -} - -/* ========================== * - * Format - * ========================== */ - -INTERNAL struct json_val json_format_internal(struct arena *arena, const struct json_ir *ir) -{ - __prof; - struct json_val val = { 0 }; - - switch (ir->type) { - case JSON_TYPE_OBJECT: { - u32 count = ir->val.children.child_count; - u32 capacity = (u32)((f64)count * OBJECT_HASH_TABLE_CAPACITY_FACTOR); - - u64 hash_table_size = sizeof(u32) * capacity; - u64 entries_size = sizeof(struct json_object_entry) * count; - - /* Not aligning entries, since get_object_entries currently - * depends on hash_table being situated right before the - * entries array in memory. */ - arena_align(arena, ALIGNOF(u32)); - u32 *hash_table = (u32 *)arena_push_array_zero(arena, u8, hash_table_size); - struct json_object_entry *entries = (struct json_object_entry *)arena_push_array_zero(arena, u8, entries_size); - - val = (struct json_val) { - .type = JSON_TYPE_OBJECT, - .child_count = count, - .val.object_table = hash_table - }; - - struct json_ir *cur = ir->val.children.child_first; - u32 index = 0; - while (cur) { - entries[index] = (struct json_object_entry) { - .key = string_copy(arena, cur->key), - .value = json_format_internal(arena, cur) - }; - - u64 hash = hash_fnv64(HASH_FNV64_SEED, BUFFER_FROM_STRING(cur->key)); - u32 slot_index_home = hash % capacity; - u32 slot_index = slot_index_home; - while (true) { - u32 *temp = &hash_table[slot_index]; - if (*temp) { - /* Occupied, linear probe for next slot */ - if (slot_index < (capacity - 1)) { - ++slot_index; + if (parent_json) { + if (parent_json->type == JSON_TYPE_OBJECT) { + /* Parse key */ + if (p->t->type == TOKEN_TYPE_STRING) { + struct string t_text = (struct string) { .len = p->t->end - p->t->start, .text = &src.text[p->t->start] }; + struct string error = { 0 }; + struct string key = interpret_string(arena, t_text, &error); + if (error.len > 0) { + push_error(arena, p, p->t, error); + goto abort; } else { - slot_index = 0; + json->key = key; + p->t = p->t->next; } - /* Table loop around should be impossible */ - ASSERT(slot_index != slot_index_home); } else { - /* Empty */ - *temp = index + 1; - break; + push_error(arena, p, p->t, STR("Key expected.")); + goto abort; + } + + /* Parse colon */ + if (p->t->type == TOKEN_TYPE_COLON) { + p->t = p->t->next; + } else { + push_error(arena, p, p->t, STR("Colon expected.")); + goto abort; } } - cur = cur->next_child; - ++index; + if (parent_json->child_last) { + parent_json->child_last->next = json; + } else { + parent_json->child_first = json; + } + parent_json->child_last = json; } - } break; - case JSON_TYPE_ARRAY: { - u32 count = ir->val.children.child_count; + /* Parse value */ + switch (p->t->type) { + case TOKEN_TYPE_NUMBER: { + struct string t_text = (struct string) { .len = p->t->end - p->t->start, .text = &src.text[p->t->start] }; + f64 value = interpret_number(t_text); + json->type = JSON_TYPE_NUMBER; + json->value.number = value; + p->t = p->t->next; + } break; - val = (struct json_val) { - .type = JSON_TYPE_ARRAY, - .val.array_children = arena_push_array(arena, struct json_val, count), - .child_count = count - }; + case TOKEN_TYPE_STRING: { + struct string t_text = (struct string) { .len = p->t->end - p->t->start, .text = &src.text[p->t->start] }; + struct string error = { 0 }; + struct string value = interpret_string(arena, t_text, &error); + if (error.len > 0) { + push_error(arena, p, p->t, error); + goto abort; + } else { + json->type = JSON_TYPE_STRING; + json->value.string = value; + p->t = p->t->next; + } + } break; - struct json_ir *child_ir = ir->val.children.child_first; - u64 child_index = 0; - while (child_ir) { - val.val.array_children[child_index] = json_format_internal(arena, child_ir); - child_ir = child_ir->next_child; - ++child_index; + case TOKEN_TYPE_TRUE: { + json->type = JSON_TYPE_BOOL; + json->value.boolean = true; + p->t = p->t->next; + } break; + + case TOKEN_TYPE_FALSE: { + json->type = JSON_TYPE_BOOL; + json->value.boolean = false; + p->t = p->t->next; + } break; + + case TOKEN_TYPE_NULL: { + json->type = JSON_TYPE_NULL; + p->t = p->t->next; + } break; + + case TOKEN_TYPE_CURLY_BRACE_OPEN: { + json->type = JSON_TYPE_OBJECT; + p->t = p->t->next; + is_new_parent = true; + } break; + + case TOKEN_TYPE_SQUARE_BRACE_OPEN: { + json->type = JSON_TYPE_ARRAY; + p->t = p->t->next; + is_new_parent = true; + } break; + + default: { + push_error(arena, p, p->t, STR("Value expected.")); + p->t = p->t->next; + goto abort; + } break; + } + } + + if (is_new_parent) { + /* Push self back to stack to re-check for closing brace later */ + *arena_push(scratch.arena, struct json *) = json; + ++stack_count; + + /* Create child & push to stack */ + struct json *child = arena_push(arena, struct json); + child->parent = json; + *arena_push(scratch.arena, struct json *) = child; + ++stack_count; + } else if (parent_json) { + /* Check for comma */ + if (p->t->type == TOKEN_TYPE_COMMA) { + /* Create sibling & push to stack */ + struct json *sibling = arena_push(arena, struct json); + sibling->parent = parent_json; + *arena_push(scratch.arena, struct json *) = sibling; + ++stack_count; + p->t = p->t->next; } - } break; - - case JSON_TYPE_STRING: { - struct string *str = arena_push(arena, struct string); - *str = string_copy(arena, ir->val.string); - val = (struct json_val){ - .type = JSON_TYPE_STRING, - .val.string = str - }; - } break; - - case JSON_TYPE_BOOL: { - val = (struct json_val){ - .type = JSON_TYPE_BOOL, - .val.boolean = ir->val.boolean - }; - } break; - - case JSON_TYPE_NULL: { - val = (struct json_val){ - .type = JSON_TYPE_NULL - }; - } break; - - case JSON_TYPE_NUMBER: { - val = (struct json_val){ - .type = JSON_TYPE_NUMBER, - .val.number = ir->val.number - }; - } break; - - case JSON_TYPE_INVALID: { - ASSERT(false); - return (struct json_val) { 0 }; - } break; - } - - return val; -} - -const struct json_val *json_format(struct arena *arena, const struct json_ir *ir) -{ - __prof; - struct json_val *root_obj = arena_push(arena, struct json_val); - *root_obj = json_format_internal(arena, ir); - return root_obj; -} - -/* Returns NULL on error (and sets `error` string parameter) */ -const struct json_val *json_parse_and_format(struct arena *arena, struct buffer bytes, struct string **error) -{ - __prof; - struct temp_arena scratch = scratch_begin(arena); - - struct string *error_temp = NULL; - const struct json_ir *root_ir = json_parse(scratch.arena, bytes, &error_temp); - - struct json_val *root_val = NULL; - if (error_temp) { - if (error) { - /* Copy error message out of scratch */ - *error = arena_push(arena, struct string); - **error = string_copy(arena, *error_temp); } - } else { - root_val = arena_push(arena, struct json_val); - *root_val = json_format_internal(arena, root_ir); } - /* Decommit since the immediate-representation memory usage may have been high */ - scratch_end_and_decommit(scratch); - - return root_val; +abort: + scratch_end(scratch); + return res; } /* ========================== * - * Index + * Interface * ========================== */ -INTERNAL struct json_object_entry *get_object_entries(const struct json_val *v) +struct json_parse_result json_from_string(struct arena *arena, struct string src) { - __prof; - u32 hash_table_size = (u32)((u32)sizeof(u32) * (u32)(v->child_count * OBJECT_HASH_TABLE_CAPACITY_FACTOR)); - return (struct json_object_entry *)((u8 *)v->val.object_table + hash_table_size); -} - -const struct json_val *json_array_get(const struct json_val *obj, u32 index) -{ - __prof; - ASSERT(json_is_array(obj)); - ASSERT(index < obj->child_count); - return &obj->val.array_children[index]; -} - -const struct json_val *json_object_get(const struct json_val *obj, struct string key) -{ - __prof; - ASSERT(json_is_object(obj)); - - u32 count = obj->child_count; - u32 capacity = (u32)(count * OBJECT_HASH_TABLE_CAPACITY_FACTOR); - - if (count <= 0) { - return NULL; - } - - u32 *hash_table = (u32 *)obj->val.object_table; - const struct json_object_entry *entries = get_object_entries(obj); - - u64 hash = hash_fnv64(HASH_FNV64_SEED, BUFFER_FROM_STRING(key)); - u32 slot_index_home = hash % capacity; - u32 slot_index = slot_index_home; - while (true) { - u32 *temp = &hash_table[slot_index]; - if (*temp) { - const struct json_object_entry *entry = &entries[*temp - 1]; - if (string_eq(entry->key, key)) { - /* Match */ - return &entry->value; - } else { - /* Not matching, linear probe next slot */ - if (slot_index < (capacity - 1)) { - ++slot_index; - } else { - slot_index = 0; - } - /* Table loop around means not found */ - if (slot_index == slot_index_home) { - return NULL; - } - } - } else { - /* Not found */ - return NULL; - } - } -} - -const struct json_object_entry *json_object_get_index(const struct json_val *obj, u32 index) -{ - __prof; - ASSERT(json_is_object(obj)); - ASSERT(index < obj->child_count); - return &(get_object_entries(obj)[index]); -} - -/* ========================== * - * Dump - * ========================== */ - -INTERNAL struct string json_dump_to_string_internal(struct arena *arena, const struct string *key, const struct json_val *val, u32 indent, u32 level) -{ - __prof; struct temp_arena scratch = scratch_begin(arena); + struct lex_result lex_res = lex(scratch.arena, src); + struct parser p = { + .src = src, + .t = lex_res.token_first, + .errors = { 0 } + }; - if (!val) { - return (struct string) { 0 }; - } + /* Parse root */ + struct json *root = parse(arena, &p); - u8 *final_text = arena_dry_push(arena, u8); - u64 final_len = 0; - - u32 indent_len = indent * level; - final_len += string_repeat(arena, STR(" "), indent_len).len; - - if (key) { - final_len += string_format( - arena, - STR("\"%F\": "), - FMT_STR(*key) - ).len; - } - - switch (val->type) { - case JSON_TYPE_OBJECT: { - u32 count = val->child_count; - u32 last_entry_index = (u32)max_u64(count, 1) - 1; - const struct json_object_entry *entries = get_object_entries(val); - final_len += string_copy(arena, STR("{\n")).len; - for (u32 i = 0; i < count; ++i) { - const struct json_object_entry *entry = &entries[i]; - struct string value_str = json_dump_to_string_internal(arena, &entry->key, &entry->value, indent, level + 1); - final_len += value_str.len; - if (i != last_entry_index) { - final_len += string_copy(arena, STR(",\n")).len; - } else { - final_len += string_copy(arena, STR("\n")).len; - } - } - final_len += string_repeat(arena, STR(" "), indent_len).len; - final_len += string_copy(arena, STR("}")).len; - } break; - - case JSON_TYPE_ARRAY: { - u32 count = val->child_count; - u32 last_entry_index = (u32)max_u64(count, 1) - 1; - - final_len += string_copy(arena, STR("[\n")).len; - for (u32 i = 0; i < count; ++i) { - const struct json_val *child = &val->val.array_children[i]; - struct string value_str = json_dump_to_string_internal(arena, NULL, child, indent, level + 1); - final_len += value_str.len; - if (i != last_entry_index) { - final_len += string_copy(arena, STR(",\n")).len; - } else { - final_len += string_copy(arena, STR("\n")).len; - } - } - final_len += string_repeat(arena, STR(" "), indent_len).len; - final_len += string_copy(arena, STR("]")).len; - } break; - - case JSON_TYPE_STRING: { - final_len += string_format(arena, STR("\"%F\""), FMT_STR(*val->val.string)).len; - } break; - - case JSON_TYPE_NUMBER: { - /* Precision = 15 */ - const struct string precision_str = STR(".00000000000000"); - - struct string num_str = string_from_float(scratch.arena, val->val.number, (u32)precision_str.len - 1); - if (string_ends_with(num_str, precision_str)) { - num_str.len -= precision_str.len; - } - final_len += string_copy(arena, num_str).len; - } break; - - case JSON_TYPE_NULL: { - final_len += string_copy(arena, STR("null")).len; - } break; - - case JSON_TYPE_BOOL: { - struct string str = val->val.boolean ? STR("true") : STR("false"); - final_len += string_copy(arena, str).len; - } break; - - case JSON_TYPE_INVALID: { - /* Unknown type */ - ASSERT(false); - return (struct string) { 0 }; - } break; + /* Verify end of file */ + if (p.errors.count == 0 && p.t->type != TOKEN_TYPE_EOF) { + push_error(arena, &p, p.t, STR("Expected end of file.")); } scratch_end(scratch); - return (struct string) { - .text = final_text, - .len = final_len + return (struct json_parse_result) { + .root = root, + .errors = p.errors }; } - -struct string json_dump_to_string(struct arena *arena, const struct json_val *val, u32 indent) -{ - __prof; - return json_dump_to_string_internal(arena, NULL, val, indent, 0); -} - -/* ========================== * - * Write - * ========================== */ - -struct json_ir *json_ir_object(struct arena *arena) -{ - __prof; - struct json_ir *ir = arena_push(arena, struct json_ir); - *ir = (struct json_ir) { - .type = JSON_TYPE_OBJECT - }; - return ir; -} - -struct json_ir *json_ir_number(struct arena *arena, f64 n) -{ - __prof; - struct json_ir *ir = arena_push(arena, struct json_ir); - *ir = (struct json_ir) { - .type = JSON_TYPE_NUMBER, - .val.number = n - }; - return ir; -} - -struct json_ir *json_ir_bool(struct arena *arena, b32 b) -{ - __prof; - struct json_ir *ir = arena_push(arena, struct json_ir); - *ir = (struct json_ir) { - .type = JSON_TYPE_BOOL, - .val.boolean = b - }; - return ir; -} - -#if 0 -/* NOTE: Does NOT copy `str` text */ -struct json_ir *json_ir_string(struct arena *arena, struct string str) -{ - __prof; - struct json_ir *ir = ARENA_PUSH_STRUCT(arena, struct json_ir); - *ir = (struct json_ir) { - .type = JSON_TYPE_STRING, - .v_string = str - }; - return ir; -} -#endif - -struct json_ir *json_ir_object_set(struct json_ir *obj, struct string key, struct json_ir *value) -{ - __prof; - ASSERT(obj->type == JSON_TYPE_OBJECT); - - /* Value already set previously */ - ASSERT(value->key.len == 0 && value->key.text == NULL); - - value->key = key; - - if (obj->val.children.child_last) { - obj->val.children.child_last->next_child = value; - } else if (!obj->val.children.child_first) { - obj->val.children.child_first = value; - } - obj->val.children.child_last = value; - - obj->val.children.child_count++; - - return value; -} diff --git a/src/json.h b/src/json.h index 6b684307..74b45460 100644 --- a/src/json.h +++ b/src/json.h @@ -2,142 +2,48 @@ #define JSON_H enum json_type { - JSON_TYPE_INVALID, - - JSON_TYPE_STRING, JSON_TYPE_NULL, JSON_TYPE_BOOL, + JSON_TYPE_NUMBER, + JSON_TYPE_STRING, JSON_TYPE_ARRAY, - JSON_TYPE_OBJECT, - JSON_TYPE_NUMBER + JSON_TYPE_OBJECT }; -struct json_object_entry; - -struct json_ir_parent_data { - u32 child_count; - struct json_ir *child_first; - struct json_ir *child_last; -}; - -/* Intermediate representation of JSON hierarchy tree in memory. Used for mutating - * JSON in parsing stage or for creating new objects. Should be manipulated via the - * API. */ -struct json_ir { +struct json { enum json_type type; - struct json_ir *next_child; struct string key; + + struct json *parent; + struct json *next; + struct json *child_first; + struct json *child_last; + union { - struct json_ir_parent_data children; - b32 boolean; struct string string; f64 number; - } val; -}; - -/* Final representation of JSON hierarchy. Should be manipulated via the API. */ -struct json_val { - enum json_type type; - u32 child_count; - union { - void *object_table; - struct json_val *array_children; b32 boolean; - struct string *string; - f64 number; - } val; + } value; }; -struct json_object_entry { - struct string key; - struct json_val value; +struct json_error { + struct string msg; + u64 start; + u64 end; + struct json_error *next; }; -/* Parse */ -const struct json_ir *json_parse(struct arena *arena, struct buffer bytes, struct string **error); +struct json_error_list { + u64 count; + struct json_error *first; + struct json_error *last; +}; -/* Format */ -const struct json_val *json_format(struct arena *arena, const struct json_ir *ir); -const struct json_val *json_parse_and_format(struct arena *arena, struct buffer bytes, struct string **error); +struct json_parse_result { + struct json *root; + struct json_error_list errors; +}; -/* Index */ -const struct json_val *json_array_get(const struct json_val *a, u32 index); -const struct json_val *json_object_get(const struct json_val *obj, struct string key); -const struct json_object_entry *json_object_get_index(const struct json_val *obj, u32 index); - -/* Dump */ -struct string json_dump_to_string(struct arena *arena, const struct json_val *val, u32 indent); - -/* Write */ -struct json_ir *json_ir_object(struct arena *arena); -struct json_ir *json_ir_number(struct arena *arena, f64 n); -struct json_ir *json_ir_bool(struct arena *arena, b32 b); -struct json_ir *json_ir_object_set(struct json_ir *obj, struct string key, struct json_ir *value); - -/* ========================== * - * Type util - * ========================== */ - -INLINE b32 json_is_object(const struct json_val *v) -{ - return v && v->type == JSON_TYPE_OBJECT; -} - -INLINE b32 json_is_string(const struct json_val *v) -{ - return v && v->type == JSON_TYPE_STRING; -} - -INLINE b32 json_is_number(const struct json_val *v) -{ - return v && v->type == JSON_TYPE_NUMBER; -} - -INLINE b32 json_is_array(const struct json_val *v) -{ - return v && v->type == JSON_TYPE_ARRAY; -} - -INLINE b32 json_is_bool(const struct json_val *v) -{ - return v && v->type == JSON_TYPE_BOOL; -} - -INLINE b32 json_is_null(const struct json_val *v) -{ - return v && v->type == JSON_TYPE_NULL; -} - -/* ========================== * - * Val util - * ========================== */ - -INLINE struct string json_string(const struct json_val *v) -{ - ASSERT(json_is_string(v)); - return *v->val.string; -} - -INLINE f64 json_number(const struct json_val *v) -{ - ASSERT(json_is_number(v)); - return v->val.number; -} - -INLINE b32 json_bool(const struct json_val *v) -{ - ASSERT(json_is_bool(v)); - return v->val.boolean; -} - -/* ========================== * - * Parent util - * ========================== */ - -INLINE u32 json_child_count(const struct json_val *v) -{ - ASSERT(json_is_object(v) || json_is_array(v)); - return v->child_count; -} +struct json_parse_result json_from_string(struct arena *arena, struct string src); #endif diff --git a/src/settings.c b/src/settings.c index 984a6b10..a6345cfa 100644 --- a/src/settings.c +++ b/src/settings.c @@ -1,158 +1,156 @@ #include "settings.h" -#include "sys.h" #include "json.h" -#include "scratch.h" #include "string.h" -#include "app.h" +#include "arena.h" +#include "scratch.h" #include "math.h" -/* TODO: - * Rework the whole settings system to be not so rigid. - * - Multi-threaded - * - Current settings state is readable & modifiable at any time - * - Can be done w/ a settings_open / settings_close block - * - Allow for dot notation in settings queries (should probably be implemented in the json layer). - * - "." denotes subindexing, IE: settings_get("window.width") (numbers can also be implemented for arrays). - * - "\." escapable - */ +struct buffer settings_serialize(struct arena *arena, const struct sys_window_settings *settings) +{ + __prof; -#define SETTINGS_FILENAME "settings.json" + struct string minimized = settings->flags & SYS_WINDOW_SETTINGS_FLAG_MINIMIZED ? STR("true") : STR("false"); + struct string maximized = settings->flags & SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED ? STR("true") : STR("false"); + struct string fullscreen = settings->flags & SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN ? STR("true") : STR("false"); + i32 x = settings->floating_x; + i32 y = settings->floating_y; + i32 width = settings->floating_width; + i32 height = settings->floating_height; -INTERNAL struct buffer serialize_window_settings(struct arena *arena, const struct sys_window_settings *settings) + struct string fmt = STR( + "{\n" + " \"window\": {\n" + " \"minimized\": %F,\n" + " \"maximized\": %F,\n" + " \"fullscreen\": %F,\n" + " \"x\": %F,\n" + " \"y\": %F,\n" + " \"width\": %F,\n" + " \"height\": %F\n" + " }\n" + "}\n" + ); + + struct string formatted = string_format(arena, + fmt, + FMT_STR(minimized), + FMT_STR(maximized), + FMT_STR(fullscreen), + FMT_SINT(x), + FMT_SINT(y), + FMT_SINT(width), + FMT_SINT(height)); + + return BUFFER_FROM_STRING(formatted); +} + +struct sys_window_settings *settings_deserialize(struct arena *arena, struct buffer src, struct string *error_out) { __prof; struct temp_arena scratch = scratch_begin(arena); - struct json_ir *root_obj = json_ir_object(scratch.arena); - struct json_ir *window_settings_obj = json_ir_object_set(root_obj, STR("window"), json_ir_object(scratch.arena)); - json_ir_object_set(window_settings_obj, STR("minimized"), json_ir_bool(scratch.arena, settings->flags & SYS_WINDOW_SETTINGS_FLAG_MINIMIZED)); - json_ir_object_set(window_settings_obj, STR("maximized"), json_ir_bool(scratch.arena, settings->flags & SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED)); - json_ir_object_set(window_settings_obj, STR("fullscreen"), json_ir_bool(scratch.arena, settings->flags & SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN)); - json_ir_object_set(window_settings_obj, STR("x"), json_ir_number(scratch.arena, settings->floating_x)); - json_ir_object_set(window_settings_obj, STR("y"), json_ir_number(scratch.arena, settings->floating_y)); - json_ir_object_set(window_settings_obj, STR("width"), json_ir_number(scratch.arena, settings->floating_width)); - json_ir_object_set(window_settings_obj, STR("height"), json_ir_number(scratch.arena, settings->floating_height)); - const struct json_val *formatted = json_format(scratch.arena, root_obj); - struct buffer buff = BUFFER_FROM_STRING(json_dump_to_string(arena, formatted, 2)); - - scratch_end(scratch); - return buff; -} - -INTERNAL void deserialize_window_settings(struct buffer json_bytes, struct sys_window_settings *settings) -{ - __prof; - struct temp_arena scratch = scratch_begin_no_conflict(); struct string error = { 0 }; + struct json_error json_error = { 0 }; - if (json_bytes.size <= 0) { - goto end; + struct sys_window_settings *settings = arena_push_zero(arena, struct sys_window_settings); + struct json_parse_result parse_res = json_from_string(scratch.arena, STRING_FROM_BUFFER(src)); + + if (parse_res.errors.count > 0) { + json_error = *parse_res.errors.first; + goto abort; } - struct string *parse_error = NULL; - const struct json_val *root_json = json_parse_and_format(scratch.arena, json_bytes, &parse_error); - if (parse_error) { - error = *parse_error; - goto end; + struct json *root = parse_res.root; + if (!root) { + error = STR("Root object not found."); + goto abort; } - const struct json_val *window_settings_json = json_object_get(root_json, STR("window")); - if (!window_settings_json || !json_is_object(window_settings_json)) { - goto end; + struct json *window = root->child_first; + if (!window || window->type != JSON_TYPE_OBJECT || !string_eq(window->key, STR("window"))) { + error = STR("\"window\" object not found"); + goto abort; } - const struct json_val *maximized = json_object_get(window_settings_json, STR("maximized")); - const struct json_val *fullscreen = json_object_get(window_settings_json, STR("fullscreen")); - const struct json_val *width = json_object_get(window_settings_json, STR("width")); - const struct json_val *height = json_object_get(window_settings_json, STR("height")); - const struct json_val *x = json_object_get(window_settings_json, STR("x")); - const struct json_val *y = json_object_get(window_settings_json, STR("y")); + b32 found_maximized = false; + b32 found_fullscreen = false; + b32 found_x = false; + b32 found_y = false; + b32 found_width = false; + b32 found_height = false; + for (struct json *child = window->child_first; child; child = child->next) { + struct string key = child->key; - settings->floating_x = json_is_number(x) ? (i32)clamp_f64(json_number(x), I32_MIN, I32_MAX) : settings->floating_x; - settings->floating_y = json_is_number(y) ? (i32)clamp_f64(json_number(y), I32_MIN, I32_MAX) : settings->floating_y; - settings->floating_width = json_is_number(width) ? (i32)clamp_f64(json_number(width), I32_MIN, I32_MAX) : settings->floating_width; - settings->floating_height = json_is_number(height) ? (i32)clamp_f64(json_number(height), I32_MIN, I32_MAX) : settings->floating_height; - settings->flags = json_is_bool(maximized) ? (json_bool(maximized) ? (settings->flags | SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED) : (settings->flags & ~SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED)) : settings->flags; - settings->flags = json_is_bool(fullscreen) ? (json_bool(fullscreen) ? (settings->flags | SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN) : (settings->flags & ~SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN)) : settings->flags; + if (string_eq(key, STR("maximized"))) { + if (child->type != JSON_TYPE_BOOL) { + error = STR("Expected boolean for \"maximized\""); + goto abort; + } + if (child->value.boolean) { + settings->flags |= SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED; + } + found_maximized = true; + } else if (string_eq(key, STR("fullscreen"))) { + if (child->type != JSON_TYPE_BOOL) { + error = STR("Expected boolean for \"fulscreen\""); + goto abort; + } + if (child->value.boolean) { + settings->flags |= SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN; + } + found_fullscreen = true; + } else if (string_eq(key, STR("x"))) { + if (child->type != JSON_TYPE_NUMBER) { + error = STR("Expected number for \"x\""); + goto abort; + } + settings->floating_x = math_round(child->value.number); + found_x = true; + } else if (string_eq(key, STR("y"))) { + if (child->type != JSON_TYPE_NUMBER) { + error = STR("Expected number for \"y\""); + goto abort; + } + settings->floating_y = math_round(child->value.number); + found_y = true; + } else if (string_eq(key, STR("width"))) { + if (child->type != JSON_TYPE_NUMBER) { + error = STR("Expected number for \"width\""); + goto abort; + } + settings->floating_width = math_round(child->value.number); + found_width = true; + } else if (string_eq(key, STR("height"))) { + if (child->type != JSON_TYPE_NUMBER) { + error = STR("Expected number for \"height\""); + goto abort; + } + settings->floating_height = math_round(child->value.number); + found_height = true; + } + } + if (!found_maximized) { error = STR("Missing \"maximized\""); goto abort; } + if (!found_fullscreen) { error = STR("Missing \"fullscreen\""); goto abort; } + if (!found_x) { error = STR("Missing \"x\""); goto abort; } + if (!found_y) { error = STR("Missing \"y\""); goto abort; } + if (!found_width) { error = STR("Missing \"width\""); goto abort; } + if (!found_height) { error = STR("Missing \"height\""); goto abort; } -end: - if (error.len > 0) { - sys_message_box( - SYS_MESSAGE_BOX_KIND_WARNING, - string_format( - scratch.arena, - STR("Error loading settings file:\n%F"), - FMT_STR(error) - ) - ); - } - - scratch_end(scratch); -} - -struct sys_window_settings settings_default_window_settings(struct sys_window *window) -{ - __prof; - - struct v2 monitor_size = sys_window_get_monitor_size(window); - - i32 width = 1280; - i32 height = math_round(width / (f32)(DEFAULT_CAMERA_WIDTH / DEFAULT_CAMERA_HEIGHT)); - i32 x = math_round(monitor_size.x / 2.f - width / 2); - i32 y = math_round(monitor_size.y / 2.f - height / 2); - - return (struct sys_window_settings) { -#if RTC -# if DEVELOPER - .title = "Debug (Developer Build)", -# else - .title = "Debug", -# endif -#else -# if DEVELOPER - .title = "Power Play (Developer Build)", -# else - .title = "Power Play", -# endif -#endif - .floating_x = x, - .floating_y = y, - .floating_width = width, - .floating_height = height - }; -} - -void settings_read_from_file(struct sys_window_settings *default_settings) -{ - __prof; - struct temp_arena scratch = scratch_begin_no_conflict(); - - /* Read window settings file if it exists */ - struct string window_settings_path = app_write_path_cat(scratch.arena, STR(SETTINGS_FILENAME)); - if (sys_is_file(window_settings_path)) { - struct sys_file settings_file = sys_file_open_read(window_settings_path); - struct buffer file_data = sys_file_read_all(scratch.arena, settings_file); - sys_file_close(settings_file); - deserialize_window_settings(file_data, default_settings); - } - - scratch_end(scratch); -} - -void settings_write_to_file(const struct sys_window_settings *settings) -{ - __prof; - struct temp_arena scratch = scratch_begin_no_conflict(); - - /* Write window settings to file */ - struct buffer settings_file_data = serialize_window_settings(scratch.arena, settings); - if (settings_file_data.size > 0) { - struct string window_settings_path = app_write_path_cat(scratch.arena, STR(SETTINGS_FILENAME)); - struct sys_file settings_file = sys_file_open_write(window_settings_path); - sys_file_write(settings_file, settings_file_data); - sys_file_close(settings_file); +abort: + if (error_out && (error.len > 0 || json_error.msg.len > 0)) { + if (json_error.msg.len > 0) { + *error_out = string_format(arena, + STR("%F\n(%F:%F)"), + FMT_STR(json_error.msg), + FMT_UINT(json_error.start), + FMT_UINT(json_error.end)); + } else { + *error_out = string_copy(arena, error); + } } scratch_end(scratch); + + return settings; } diff --git a/src/settings.h b/src/settings.h index 88110ec9..e6a31c42 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,10 +1,9 @@ #ifndef SETTINGS_H #define SETTINGS_H -struct sys_window; +struct sys_window_settings; -struct sys_window_settings settings_default_window_settings(struct sys_window *window); -void settings_read_from_file(struct sys_window_settings *default_settings); -void settings_write_to_file(const struct sys_window_settings *settings); +struct buffer settings_serialize(struct arena *arena, const struct sys_window_settings *settings); +struct sys_window_settings *settings_deserialize(struct arena *arena, struct buffer src, struct string *error_out); #endif