mmmmh
This commit is contained in:
parent
82aee127b0
commit
a92b119549
99 changed files with 6922 additions and 5723 deletions
386
json.c
Normal file
386
json.c
Normal file
|
|
@ -0,0 +1,386 @@
|
|||
#include "json.h"
|
||||
|
||||
#include "warnings/colla_warn_beg.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "strstream.h"
|
||||
#include "file.h"
|
||||
#include "tracelog.h"
|
||||
|
||||
// #define json__logv() warn("%s:%d", __FILE__, __LINE__)
|
||||
#define json__logv()
|
||||
#define json__ensure(c) json__check_char(in, c)
|
||||
|
||||
static bool json__check_char(instream_t *in, char c) {
|
||||
if (istrGet(in) == c) {
|
||||
return true;
|
||||
}
|
||||
istrRewindN(in, 1);
|
||||
err("wrong character at %zu, should be '%c' but is 0x%02x '%c'", istrTell(*in), c, istrPeek(in), istrPeek(in));
|
||||
json__logv();
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags, jsonval_t **out);
|
||||
static bool json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags, jsonval_t **out);
|
||||
|
||||
static bool json__is_value_finished(instream_t *in) {
|
||||
usize old_pos = istrTell(*in);
|
||||
|
||||
istrSkipWhitespace(in);
|
||||
switch(istrPeek(in)) {
|
||||
case '}': // fallthrough
|
||||
case ']': // fallthrough
|
||||
case ',':
|
||||
return true;
|
||||
}
|
||||
|
||||
in->cur = in->start + old_pos;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool json__parse_null(instream_t *in) {
|
||||
strview_t null_view = istrGetViewLen(in, 4);
|
||||
bool is_valid = true;
|
||||
|
||||
if (!strvEquals(null_view, strv("null"))) {
|
||||
err("should be null but is: (%.*s) at %zu", null_view.len, null_view.buf, istrTell(*in));
|
||||
is_valid = false;
|
||||
}
|
||||
|
||||
if (!json__is_value_finished(in)) {
|
||||
err("null, should be finished, but isn't at %zu", istrTell(*in));
|
||||
is_valid = false;
|
||||
}
|
||||
|
||||
if (!is_valid) json__logv();
|
||||
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
static bool json__parse_array(arena_t *arena, instream_t *in, jsonflags_e flags, jsonval_t **out) {
|
||||
jsonval_t *head = NULL;
|
||||
|
||||
if (!json__ensure('[')) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
istrSkipWhitespace(in);
|
||||
|
||||
// if it is an empty array
|
||||
if (istrPeek(in) == ']') {
|
||||
istrSkip(in, 1);
|
||||
goto success;
|
||||
}
|
||||
|
||||
if (!json__parse_value(arena, in, flags, &head)) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
jsonval_t *cur = head;
|
||||
|
||||
while (true) {
|
||||
istrSkipWhitespace(in);
|
||||
switch (istrGet(in)) {
|
||||
case ']':
|
||||
return head;
|
||||
case ',':
|
||||
{
|
||||
istrSkipWhitespace(in);
|
||||
// trailing comma
|
||||
if (istrPeek(in) == ']') {
|
||||
if (flags & JSON_NO_TRAILING_COMMAS) {
|
||||
err("trailing comma in array at at %zu: (%c)(%d)", istrTell(*in), *in->cur, *in->cur);
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
jsonval_t *next = NULL;
|
||||
if (!json__parse_value(arena, in, flags, &next)) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
cur->next = next;
|
||||
next->prev = cur;
|
||||
cur = next;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
istrRewindN(in, 1);
|
||||
err("unknown char after array at %zu: (%c)(%d)", istrTell(*in), *in->cur, *in->cur);
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
success:
|
||||
*out = head;
|
||||
return true;
|
||||
fail:
|
||||
*out = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool json__parse_string(arena_t *arena, instream_t *in, str_t *out) {
|
||||
istrSkipWhitespace(in);
|
||||
|
||||
if (!json__ensure('"')) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
const char *from = in->cur;
|
||||
|
||||
for (; !istrIsFinished(*in) && *in->cur != '"'; ++in->cur) {
|
||||
if (istrPeek(in) == '\\') {
|
||||
++in->cur;
|
||||
}
|
||||
}
|
||||
|
||||
usize len = in->cur - from;
|
||||
|
||||
*out = str(arena, from, len);
|
||||
|
||||
if (!json__ensure('"')) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return true;
|
||||
fail:
|
||||
*out = STR_EMPTY;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool json__parse_number(instream_t *in, double *out) {
|
||||
return istrGetDouble(in, out);
|
||||
}
|
||||
|
||||
static bool json__parse_bool(instream_t *in, bool *out) {
|
||||
size_t remaining = istrRemaining(*in);
|
||||
if (remaining >= 4 && memcmp(in->cur, "true", 4) == 0) {
|
||||
istrSkip(in, 4);
|
||||
*out = true;
|
||||
}
|
||||
else if (remaining >= 5 && memcmp(in->cur, "false", 5) == 0) {
|
||||
istrSkip(in, 5);
|
||||
*out = false;
|
||||
}
|
||||
else {
|
||||
err("unknown boolean at %zu: %.10s", istrTell(*in), in->cur);
|
||||
json__logv();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool json__parse_obj(arena_t *arena, instream_t *in, jsonflags_e flags, jsonval_t **out) {
|
||||
if (!json__ensure('{')) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
istrSkipWhitespace(in);
|
||||
|
||||
// if it is an empty object
|
||||
if (istrPeek(in) == '}') {
|
||||
istrSkip(in, 1);
|
||||
*out = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
jsonval_t *head = NULL;
|
||||
if (!json__parse_pair(arena, in, flags, &head)) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
jsonval_t *cur = head;
|
||||
|
||||
while (true) {
|
||||
istrSkipWhitespace(in);
|
||||
switch (istrGet(in)) {
|
||||
case '}':
|
||||
goto success;
|
||||
case ',':
|
||||
{
|
||||
istrSkipWhitespace(in);
|
||||
// trailing commas
|
||||
if (!(flags & JSON_NO_TRAILING_COMMAS) && istrPeek(in) == '}') {
|
||||
goto success;
|
||||
}
|
||||
|
||||
jsonval_t *next = NULL;
|
||||
if (!json__parse_pair(arena, in, flags, &next)) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
cur->next = next;
|
||||
next->prev = cur;
|
||||
cur = next;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
istrRewindN(in, 1);
|
||||
err("unknown char after object at %zu: (%c)(%d)", istrTell(*in), *in->cur, *in->cur);
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
success:
|
||||
*out = head;
|
||||
return true;
|
||||
fail:
|
||||
*out = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags, jsonval_t **out) {
|
||||
str_t key = {0};
|
||||
if (!json__parse_string(arena, in, &key)) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// skip preamble
|
||||
istrSkipWhitespace(in);
|
||||
if (!json__ensure(':')) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!json__parse_value(arena, in, flags, out)) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
(*out)->key = key;
|
||||
return true;
|
||||
|
||||
fail:
|
||||
*out = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags, jsonval_t **out) {
|
||||
jsonval_t *val = alloc(arena, jsonval_t);
|
||||
|
||||
istrSkipWhitespace(in);
|
||||
|
||||
switch (istrPeek(in)) {
|
||||
// object
|
||||
case '{':
|
||||
if (!json__parse_obj(arena, in, flags, &val->object)) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
val->type = JSON_OBJECT;
|
||||
break;
|
||||
// array
|
||||
case '[':
|
||||
if (!json__parse_array(arena, in, flags, &val->array)) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
val->type = JSON_ARRAY;
|
||||
break;
|
||||
// string
|
||||
case '"':
|
||||
if (!json__parse_string(arena, in, &val->string)) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
val->type = JSON_STRING;
|
||||
break;
|
||||
// boolean
|
||||
case 't': // fallthrough
|
||||
case 'f':
|
||||
if (!json__parse_bool(in, &val->boolean)) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
val->type = JSON_BOOL;
|
||||
break;
|
||||
// null
|
||||
case 'n':
|
||||
if (!json__parse_null(in)) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
val->type = JSON_NULL;
|
||||
break;
|
||||
// comment
|
||||
case '/':
|
||||
err("TODO comments");
|
||||
break;
|
||||
// number
|
||||
default:
|
||||
if (!json__parse_number(in, &val->number)) {
|
||||
json__logv();
|
||||
goto fail;
|
||||
}
|
||||
val->type = JSON_NUMBER;
|
||||
break;
|
||||
}
|
||||
|
||||
*out = val;
|
||||
return true;
|
||||
fail:
|
||||
*out = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
json_t jsonParse(arena_t *arena, arena_t scratch, strview_t filename, jsonflags_e flags) {
|
||||
str_t data = fileReadWholeStr(&scratch, filename);
|
||||
return NULL;
|
||||
json_t json = jsonParseStr(arena, strv(data), flags);
|
||||
return json;
|
||||
}
|
||||
|
||||
json_t jsonParseStr(arena_t *arena, strview_t jsonstr, jsonflags_e flags) {
|
||||
arena_t before = *arena;
|
||||
|
||||
jsonval_t *root = alloc(arena, jsonval_t);
|
||||
root->type = JSON_OBJECT;
|
||||
|
||||
instream_t in = istrInitLen(jsonstr.buf, jsonstr.len);
|
||||
|
||||
if (!json__parse_obj(arena, &in, flags, &root->object)) {
|
||||
// reset arena
|
||||
*arena = before;
|
||||
json__logv();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
jsonval_t *jsonGet(jsonval_t *node, strview_t key) {
|
||||
if (!node) return NULL;
|
||||
|
||||
if (node->type != JSON_OBJECT) {
|
||||
err("passed type is not an object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node = node->object;
|
||||
|
||||
while (node) {
|
||||
if (strvEquals(strv(node->key), key)) {
|
||||
return node;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#include "warnings/colla_warn_end.h"
|
||||
Loading…
Add table
Add a link
Reference in a new issue