diff --git a/rtjson.h b/rtjson.h index d752fe3..6ef0c0c 100644 --- a/rtjson.h +++ b/rtjson.h @@ -37,6 +37,8 @@ typedef enum JSON_TYPE_STRING, JSON_TYPE_INTEGER, JSON_TYPE_FLOAT, + JSON_TYPE_BOOL, + JSON_TYPE_NULL, } json_type; /* Represents a json value */ @@ -54,6 +56,7 @@ typedef struct json s8 s; i64 i; f64 f; + b32 b; /* The first subobject. * Element 0 of an array, @@ -83,7 +86,7 @@ RTJ_API json *GetJSONArrayElement(json *a, isize i); RTJ_API isize GetJSONArrayLength(json *a); /* Iterates over every member of an object or array */ -#define JsonForEach(_ChildVarName, _ParentPtr) \ +#define JSONForEach(_ChildVarName, _ParentPtr) \ for (json *_ChildVarName = (_ParentPtr)->value.first_child; _ChildVarName != 0; _ChildVarName = _ChildVarName->next) #endif @@ -319,13 +322,97 @@ ConsumeWhitespace(s8 text, isize *_at, isize *current_line) } \ (_At) += 1; +static json *ParseJSONImpl(s8 text, isize *_at, isize *current_line, s8 file, arena *a); + static json * -ParseJSONImpl(s8 text, isize *_at, json *sibling, isize *current_line, s8 file, arena *a) +ParseValue(s8 text, isize *_at, isize *current_line, s8 file, arena *a) +{ + json *child = NULL; + isize at = *_at; + ConsumeWhitespaceNoEof(text, &at, current_line, file, 0); + if (text.data[at] == '{' || text.data[at] == '[') + { + /* Object or array, recurse into it. */ + child = ParseJSONImpl(text, &at, current_line, file, a); + if (!child) + return NULL; + } + else if (text.data[at] == '\"') + { + child = alloc(a, json); + child->value.s = ParseString(text, &at, current_line, file, a); + if (!child->value.s.data) + return NULL; + child->type = JSON_TYPE_STRING; + } + else if ((text.data[at] >= '0' && text.data[at] <= '9') || text.data[at] == '-') + { + /* Number. */ + f64 f; + i64 i; + parse_number_result res = ParseNumber(text, &at, current_line, file, &i, &f); + if (res == NOT_A_NUMBER) + return NULL; + child = alloc(a, json); + if (res == INT) + { + child->value.i = i; + child->type = JSON_TYPE_INTEGER; + } + else /* DOUBLE */ + { + child->value.f = f; + child->type = JSON_TYPE_FLOAT; + } + } + else if (text.data[at] == 't' || text.data[at] == 'f' || text.data[at] == 'n') + { + /* true / false / null */ + const s8 true_str = S8("true"); + const s8 false_str = S8("false"); + const s8 null_str = S8("null"); + s8 value_str = {.data = &text.data[at], .length = 1}; + ++at; + while (at < text.length && text.data[at] >= 'a' && text.data[at] <= 'z') + { + /* small-caps character form these keywords */ + ++at; + ++value_str.length; + } + child = alloc(a, json); + if (S8Equals(value_str, true_str)) + { + child->value.b = 1; + child->type = JSON_TYPE_BOOL; + } + else if (S8Equals(value_str, false_str)) + { + child->value.b = 0; + child->type = JSON_TYPE_BOOL; + } + else if (S8Equals(value_str, null_str)) + { + child->type = JSON_TYPE_NULL; + } + else + { + if (file.data) + printf("%.*s:%zu unexpected keyword.\n", (int)file.length, file.data, *current_line); + else + printf("%zu: unexpected keyword\n", *current_line); + return NULL; + } + } + *_at = at; + return child; +} + +static json * +ParseJSONImpl(s8 text, isize *_at, isize *current_line, s8 file, arena *a) { isize at = *_at; json *j = alloc(a, json); - j->next = sibling; if (text.data[at] == '{') { @@ -347,49 +434,12 @@ ParseJSONImpl(s8 text, isize *_at, json *sibling, isize *current_line, s8 file, ExpectCharacter(':', text, at, current_line, file, 0); ConsumeWhitespaceNoEof(text, &at, current_line, file, 0); - /* Value */ - if (text.data[at] == '{' || text.data[at] == '[') - { - /* Object or array, recurse into it. - * JSON objects are unordered, so it does not matter that we - * effectively reverse the order here */ - j->value.first_child = ParseJSONImpl(text, &at, j->value.first_child, current_line, file, a); - if (!j->value.first_child) - return 0; - j->value.first_child->key = key; - } - else if (text.data[at] == '\"') - { - json *child = alloc(a, json); - child->key = key; - child->type = JSON_TYPE_STRING; - child->value.s = ParseString(text, &at, current_line, file, a); - child->next = j->value.first_child; - j->value.first_child = child; - } - else if ((text.data[at] >= '0' && text.data[at] <= '9') || text.data[at] == '-') - { - /* Number. */ - f64 f; - i64 i; - parse_number_result res = ParseNumber(text, &at, current_line, file, &i, &f); - if (res == NOT_A_NUMBER) - return 0; - json *child = alloc(a, json); - child->key = key; - if (res == INT) - { - child->type = JSON_TYPE_INTEGER; - child->value.i = i; - } - else /* DOUBLE */ - { - child->type = JSON_TYPE_FLOAT; - child->value.f = f; - } - child->next = j->value.first_child; - j->value.first_child = child; - } + json *child = ParseValue(text, &at, current_line, file, a); + if (!child) + return 0; + child->key = key; + child->next = j->value.first_child; + j->value.first_child = child; ConsumeWhitespaceNoEof(text, &at, current_line, file, 0); @@ -424,56 +474,14 @@ ParseJSONImpl(s8 text, isize *_at, json *sibling, isize *current_line, s8 file, json *last_child = NULL; while (text.data[at] != ']' && at < text.length) { - /* value [,] ... */ - ConsumeWhitespaceNoEof(text, &at, current_line, file, 0); - if (text.data[at] == '{' || text.data[at] == '[') - { - /* Object or array, recurse into it. */ - json *child = ParseJSONImpl(text, &at, NULL, current_line, file, a); - if (!child) - return 0; - if (!j->value.first_child) - j->value.first_child = child; - if (last_child) - last_child->next = child; - last_child = child; - } - else if (text.data[at] == '\"') - { - json *child = alloc(a, json); - child->value.s = ParseString(text, &at, current_line, file, a); - child->type = JSON_TYPE_STRING; - if (!j->value.first_child) - j->value.first_child = child; - if (last_child) - last_child->next = child; - last_child = child; - } - else if ((text.data[at] >= '0' && text.data[at] <= '9') || text.data[at] == '-') - { - /* Number. */ - f64 f; - i64 i; - parse_number_result res = ParseNumber(text, &at, current_line, file, &i, &f); - if (res == NOT_A_NUMBER) - return 0; - json *child = alloc(a, json); - if (res == INT) - { - child->value.i = i; - child->type = JSON_TYPE_INTEGER; - } - else /* DOUBLE */ - { - child->value.f = f; - child->type = JSON_TYPE_FLOAT; - } - if (!j->value.first_child) - j->value.first_child = child; - if (last_child) - last_child->next = child; - last_child = child; - } + json *child = ParseValue(text, &at, current_line, file, a); + if (!child) + return 0; + if (!j->value.first_child) + j->value.first_child = child; + if (last_child) + last_child->next = child; + last_child = child; ConsumeWhitespaceNoEof(text, &at, current_line, file, 0); @@ -514,7 +522,7 @@ RTJ_API json * ParseJSON(s8 text, s8 file, arena *a) { isize line = 1, at = 0; - return ParseJSONImpl(text, &at, NULL, &line, file, a); + return ParseJSONImpl(text, &at, &line, file, a); } RTJ_API json * @@ -522,7 +530,7 @@ GetJSONMember(json *o, s8 key) { if (o->type != JSON_TYPE_OBJECT) return NULL; - JsonForEach(child, o) + JSONForEach(child, o) { if (S8Equals(child->key, key)) return child; @@ -536,7 +544,7 @@ GetJSONArrayElement(json *a, isize i) if (a->type != JSON_TYPE_ARRAY) return NULL; isize at = 0; - JsonForEach(child, a) + JSONForEach(child, a) { if (at == i) return child; @@ -551,7 +559,7 @@ GetJSONArrayLength(json *a) if (a->type != JSON_TYPE_ARRAY) return -1; isize at = 0; - JsonForEach(child, a) { ++at; } + JSONForEach(child, a) { ++at; } return at; }