mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-27 16:24:07 +02:00
442 lines
11 KiB
C
442 lines
11 KiB
C
#include "json.h"
|
|
#include "../collections/vec.h"
|
|
#include "../utils/str.h"
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
typedef struct {
|
|
char* key;
|
|
JsonValue* val;
|
|
} KV;
|
|
|
|
DEFINE_VEC(JsonValue*, Arr, arr)
|
|
DEFINE_VEC(KV, Obj, obj)
|
|
|
|
struct JsonValue {
|
|
JsonType type;
|
|
union {
|
|
bool bool_val;
|
|
char* str_val;
|
|
Arr arr_val;
|
|
Obj obj_val;
|
|
};
|
|
};
|
|
|
|
bool json_is(const JsonValue* value, JsonType type)
|
|
{
|
|
return value != NULL && value->type == type;
|
|
}
|
|
|
|
bool json_bool(const JsonValue* value)
|
|
{
|
|
return value->bool_val;
|
|
}
|
|
|
|
int64_t json_int(const JsonValue* value)
|
|
{
|
|
return strtoll(value->str_val, NULL, 10);
|
|
}
|
|
|
|
double json_float(const JsonValue* value)
|
|
{
|
|
return strtof(value->str_val, NULL);
|
|
}
|
|
|
|
const char* json_string(const JsonValue* value)
|
|
{
|
|
return value->str_val;
|
|
}
|
|
|
|
size_t json_array_size(const JsonValue* value)
|
|
{
|
|
return value->arr_val.size;
|
|
}
|
|
|
|
const JsonValue* json_array_get(const JsonValue* value, size_t idx)
|
|
{
|
|
return value->arr_val.data[idx];
|
|
}
|
|
|
|
bool json_object_has(const JsonValue* value, const char* key)
|
|
{
|
|
for (size_t i = 0; i < value->obj_val.size; ++i) {
|
|
KV* kv = &value->obj_val.data[i];
|
|
if (strcmp(key, kv->key) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const JsonValue* json_object_get(const JsonValue* value, const char* key)
|
|
{
|
|
for (size_t i = 0; i < value->obj_val.size; ++i) {
|
|
KV* kv = &value->obj_val.data[i];
|
|
if (strcmp(key, kv->key) == 0) {
|
|
return kv->val;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void json_free(JsonValue* value)
|
|
{
|
|
switch (value->type) {
|
|
case JsonType_Error:
|
|
case JsonType_Null:
|
|
case JsonType_Bool:
|
|
break;
|
|
case JsonType_Number:
|
|
case JsonType_String:
|
|
free(value->str_val);
|
|
break;
|
|
case JsonType_Array:
|
|
for (size_t i = 0; i < value->arr_val.size; ++i) {
|
|
json_free(value->arr_val.data[i]);
|
|
}
|
|
arr_destroy(&value->arr_val);
|
|
break;
|
|
case JsonType_Object:
|
|
for (size_t i = 0; i < value->obj_val.size; ++i) {
|
|
free(value->obj_val.data[i].key);
|
|
json_free(value->obj_val.data[i].val);
|
|
}
|
|
obj_destroy(&value->obj_val);
|
|
break;
|
|
}
|
|
free(value);
|
|
}
|
|
|
|
JsonValue* json_parse(const char* text, size_t text_len)
|
|
{
|
|
JsonParser p;
|
|
json_parser_construct(&p, text, text_len);
|
|
JsonValue* json = json_parser_parse(&p);
|
|
json_parser_destroy(&p);
|
|
return json;
|
|
}
|
|
|
|
#define TOK_EOF '\0'
|
|
#define TOK_ERROR 'e'
|
|
#define TOK_NULL 'n'
|
|
#define TOK_FALSE 'f'
|
|
#define TOK_TRUE 't'
|
|
#define TOK_NUMBER '0'
|
|
#define TOK_STRING '"'
|
|
|
|
static inline JsonValue* alloc(JsonValue init);
|
|
static inline void lex(JsonParser* p);
|
|
static inline void lstep(JsonParser* p);
|
|
|
|
void json_parser_construct(JsonParser* p, const char* text, size_t text_len)
|
|
{
|
|
*p = (JsonParser) {
|
|
text,
|
|
text_len,
|
|
.i = 0,
|
|
.ch = text[0],
|
|
.curr_tok = TOK_EOF,
|
|
.curr_val = NULL,
|
|
};
|
|
lex(p);
|
|
}
|
|
|
|
void json_parser_destroy(JsonParser* p)
|
|
{
|
|
(void)p;
|
|
}
|
|
|
|
static inline void free_unused_arr(Arr* arr)
|
|
{
|
|
for (size_t i = 0; i < arr->size; ++i) {
|
|
json_free(arr->data[i]);
|
|
}
|
|
arr_destroy(arr);
|
|
}
|
|
|
|
static inline void free_unused_obj(Obj* obj)
|
|
{
|
|
for (size_t i = 0; i < obj->size; ++i) {
|
|
free(obj->data[i].key);
|
|
json_free(obj->data[i].val);
|
|
}
|
|
obj_destroy(obj);
|
|
}
|
|
|
|
JsonValue* json_parser_parse(JsonParser* p)
|
|
{
|
|
switch (p->curr_tok) {
|
|
case TOK_EOF:
|
|
fprintf(stderr, "error: json: unexpected eof\n");
|
|
return NULL;
|
|
case TOK_ERROR:
|
|
lex(p);
|
|
return NULL;
|
|
case TOK_NULL:
|
|
lex(p);
|
|
return alloc((JsonValue) { .type = JsonType_Null });
|
|
case TOK_FALSE:
|
|
lex(p);
|
|
return alloc(
|
|
(JsonValue) { .type = JsonType_Bool, .bool_val = false });
|
|
case TOK_TRUE:
|
|
lex(p);
|
|
return alloc(
|
|
(JsonValue) { .type = JsonType_Null, .bool_val = true });
|
|
case TOK_NUMBER: {
|
|
char* val = p->curr_val;
|
|
lex(p);
|
|
return alloc(
|
|
(JsonValue) { .type = JsonType_Number, .str_val = val });
|
|
}
|
|
case TOK_STRING: {
|
|
char* val = p->curr_val;
|
|
lex(p);
|
|
return alloc(
|
|
(JsonValue) { .type = JsonType_String, .str_val = val });
|
|
}
|
|
}
|
|
|
|
if (p->curr_tok == '[') {
|
|
lex(p);
|
|
Arr arr;
|
|
arr_construct(&arr);
|
|
{
|
|
JsonValue* value = json_parser_parse(p);
|
|
if (!value) {
|
|
free_unused_arr(&arr);
|
|
return NULL;
|
|
}
|
|
arr_push(&arr, value);
|
|
}
|
|
while (p->curr_tok != TOK_EOF && p->curr_tok != ']') {
|
|
if (p->curr_tok != ',') {
|
|
fprintf(stderr, "error: json: expected ',' in array\n");
|
|
free_unused_arr(&arr);
|
|
return NULL;
|
|
}
|
|
lex(p);
|
|
JsonValue* value = json_parser_parse(p);
|
|
if (!value) {
|
|
free_unused_arr(&arr);
|
|
return NULL;
|
|
}
|
|
arr_push(&arr, value);
|
|
}
|
|
if (p->curr_tok != ']') {
|
|
fprintf(stderr, "error: json: expected ']' after array\n");
|
|
free_unused_arr(&arr);
|
|
return NULL;
|
|
}
|
|
lex(p);
|
|
return alloc((JsonValue) { .type = JsonType_Array, .arr_val = arr });
|
|
}
|
|
|
|
if (p->curr_tok == '{') {
|
|
lex(p);
|
|
Obj obj;
|
|
obj_construct(&obj);
|
|
{
|
|
if (p->curr_tok != '"') {
|
|
fprintf(stderr, "error: json: expected '\"' in kv\n");
|
|
free_unused_obj(&obj);
|
|
return NULL;
|
|
}
|
|
char* key = p->curr_val;
|
|
lex(p);
|
|
if (p->curr_tok != ':') {
|
|
fprintf(stderr, "error: json: expected ':' in kv\n");
|
|
free_unused_obj(&obj);
|
|
return NULL;
|
|
}
|
|
lex(p);
|
|
JsonValue* value = json_parser_parse(p);
|
|
if (!value) {
|
|
free_unused_obj(&obj);
|
|
return NULL;
|
|
}
|
|
obj_push(&obj, (KV) { key, value });
|
|
}
|
|
while (p->curr_tok != TOK_EOF && p->curr_tok != '}') {
|
|
if (p->curr_tok != ',') {
|
|
fprintf(stderr, "error: json: expected ',' in object\n");
|
|
free_unused_obj(&obj);
|
|
return NULL;
|
|
}
|
|
lex(p);
|
|
|
|
if (p->curr_tok != '"') {
|
|
fprintf(stderr, "error: json: expected '\"' in kv\n");
|
|
free_unused_obj(&obj);
|
|
return NULL;
|
|
}
|
|
char* key = p->curr_val;
|
|
lex(p);
|
|
if (p->curr_tok != ':') {
|
|
fprintf(stderr, "error: json: expected ':' in kv\n");
|
|
free_unused_obj(&obj);
|
|
return NULL;
|
|
}
|
|
lex(p);
|
|
JsonValue* value = json_parser_parse(p);
|
|
if (!value) {
|
|
free_unused_obj(&obj);
|
|
return NULL;
|
|
}
|
|
obj_push(&obj, (KV) { key, value });
|
|
}
|
|
if (p->curr_tok != '}') {
|
|
fprintf(stderr, "error: json: expected '}' after object\n");
|
|
free_unused_obj(&obj);
|
|
return NULL;
|
|
}
|
|
lex(p);
|
|
return alloc((JsonValue) { .type = JsonType_Object, .obj_val = obj });
|
|
}
|
|
|
|
fprintf(stderr, "error: json: unexpeted token\n");
|
|
return NULL;
|
|
}
|
|
|
|
static inline JsonValue* alloc(JsonValue init)
|
|
{
|
|
JsonValue* value = malloc(sizeof(JsonValue));
|
|
*value = init;
|
|
return value;
|
|
}
|
|
|
|
static inline void lex(JsonParser* p)
|
|
{
|
|
if (p->i >= p->text_len) {
|
|
p->curr_tok = TOK_EOF;
|
|
return;
|
|
}
|
|
switch (p->ch) {
|
|
case ' ':
|
|
case '\t':
|
|
case '\r':
|
|
case '\n':
|
|
lstep(p);
|
|
lex(p);
|
|
return;
|
|
case '[':
|
|
case ']':
|
|
case '{':
|
|
case '}':
|
|
case ',':
|
|
case ':':
|
|
p->curr_tok = p->ch;
|
|
lstep(p);
|
|
return;
|
|
case '0':
|
|
lstep(p);
|
|
p->curr_tok = TOK_NUMBER;
|
|
p->curr_val = str_dup("0");
|
|
return;
|
|
}
|
|
if ((p->ch >= '1' && p->ch <= '9') || p->ch == '.') {
|
|
String value;
|
|
string_construct(&value);
|
|
int dec_seps = 0;
|
|
while (p->i < p->text_len
|
|
&& ((p->ch >= '0' && p->ch <= '9')
|
|
|| (p->ch == '.' && dec_seps <= 1))) {
|
|
if (p->ch == '.') {
|
|
dec_seps += 1;
|
|
}
|
|
string_push(&value, p->ch);
|
|
lstep(p);
|
|
}
|
|
char* copy = string_copy(&value);
|
|
string_destroy(&value);
|
|
|
|
p->curr_tok = TOK_NUMBER;
|
|
p->curr_val = copy;
|
|
return;
|
|
}
|
|
if ((p->ch >= 'a' && p->ch <= 'z') || (p->ch >= 'A' && p->ch <= 'Z')) {
|
|
String value;
|
|
string_construct(&value);
|
|
while (p->i < p->text_len
|
|
&& ((p->ch >= 'a' && p->ch <= 'z')
|
|
|| (p->ch >= 'A' && p->ch <= 'Z'))) {
|
|
string_push(&value, p->ch);
|
|
lstep(p);
|
|
}
|
|
if (strcmp(value.data, "null")) {
|
|
p->curr_tok = TOK_NULL;
|
|
} else if (strcmp(value.data, "false")) {
|
|
p->curr_tok = TOK_FALSE;
|
|
} else if (strcmp(value.data, "true")) {
|
|
p->curr_tok = TOK_TRUE;
|
|
} else {
|
|
fprintf(
|
|
stderr, "error: json: illegal keyword \"%s\"\n", value.data);
|
|
p->curr_tok = TOK_ERROR;
|
|
}
|
|
string_destroy(&value);
|
|
return;
|
|
}
|
|
if (p->ch == '"') {
|
|
lstep(p);
|
|
|
|
String value;
|
|
string_construct(&value);
|
|
|
|
while (p->i < p->text_len && p->text[p->i] != '"') {
|
|
if (p->text[p->i] == '\\') {
|
|
lstep(p);
|
|
if (p->i >= p->text_len)
|
|
break;
|
|
switch (p->ch) {
|
|
case '0':
|
|
string_push(&value, '\0');
|
|
break;
|
|
case 'n':
|
|
string_push(&value, '\n');
|
|
break;
|
|
case 'r':
|
|
string_push(&value, '\r');
|
|
break;
|
|
case 't':
|
|
string_push(&value, '\t');
|
|
break;
|
|
default:
|
|
string_push(&value, p->ch);
|
|
break;
|
|
}
|
|
} else {
|
|
string_push(&value, p->ch);
|
|
}
|
|
lstep(p);
|
|
}
|
|
if (p->i >= p->text_len || p->text[p->i] != '"') {
|
|
string_destroy(&value);
|
|
fprintf(stderr, "error: json: bad string\n");
|
|
p->curr_tok = TOK_ERROR;
|
|
return;
|
|
}
|
|
lstep(p);
|
|
|
|
char* copy = string_copy(&value);
|
|
string_destroy(&value);
|
|
|
|
p->curr_tok = '"';
|
|
p->curr_val = copy;
|
|
return;
|
|
}
|
|
|
|
fprintf(stderr, "error: json: illegal char '%c'\n", p->ch);
|
|
lstep(p);
|
|
p->curr_tok = TOK_ERROR;
|
|
return;
|
|
}
|
|
|
|
static inline void lstep(JsonParser* p)
|
|
{
|
|
p->i += 1;
|
|
p->ch = p->text[p->i];
|
|
}
|