rtjson: small fixes, add booleans and null

This commit is contained in:
Kevin Trogant 2026-01-14 08:18:22 +01:00
parent e65550b6e5
commit c901af0a17

208
rtjson.h
View File

@ -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;
}