minimal json parser for generated data
This commit is contained in:
parent
97be659f53
commit
ec61d20b29
17
Makefile
17
Makefile
@ -1,12 +1,17 @@
|
|||||||
CFLAGS=-g -Wall
|
CFLAGS=-lm -g -Wall -Walloc-size-larger-than=-1
|
||||||
|
|
||||||
.PHONY := gen_data
|
.PHONY := gen_data main
|
||||||
|
|
||||||
gen_data: build/gen_data.exe
|
gen_data: build/gen_data
|
||||||
|
main: build/main
|
||||||
|
|
||||||
build/gen_data.exe: src/gen_data.c
|
build/gen_data: src/gen_data.c
|
||||||
mkdir -p build
|
mkdir -p build
|
||||||
gcc -o build/gen_data.exe src/gen_data.c $(CFLAGS) -O2
|
gcc -o build/gen_data src/gen_data.c $(CFLAGS) -O2
|
||||||
|
|
||||||
|
build/main: src/main.c src/json_parser.c
|
||||||
|
mkdir -p build
|
||||||
|
gcc -o build/main src/main.c $(CFLAGS) -O2
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -r build
|
rm -r build
|
||||||
|
393
src/json_parser.c
Normal file
393
src/json_parser.c
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "json_parser.h"
|
||||||
|
|
||||||
|
#define ARRAY_LEN(x) (sizeof(x)/sizeof(x[0]))
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
|
||||||
|
void free_json_object(struct json_object *object)
|
||||||
|
{
|
||||||
|
if (object == NULL) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < object->count; i++) {
|
||||||
|
free_json_string(object->keys[i]);
|
||||||
|
free_json_value(object->values[i]);
|
||||||
|
}
|
||||||
|
free(object->keys);
|
||||||
|
free(object->values);
|
||||||
|
free(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_json_value(struct json_value *value)
|
||||||
|
{
|
||||||
|
if (value == NULL) return;
|
||||||
|
|
||||||
|
if (value->type == JSON_TYPE_OBJECT) {
|
||||||
|
free_json_object(value->object);
|
||||||
|
} else if (value->type == JSON_TYPE_ARRAY) {
|
||||||
|
free_json_array(value->array);
|
||||||
|
} else if (value->type == JSON_TYPE_STRING) {
|
||||||
|
free_json_string(value->string);
|
||||||
|
}
|
||||||
|
free(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_json_string(struct json_string *string)
|
||||||
|
{
|
||||||
|
if (string == NULL) return;
|
||||||
|
free(string->str);
|
||||||
|
free(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_json_array(struct json_array *array)
|
||||||
|
{
|
||||||
|
if (array == NULL) return;
|
||||||
|
for (int i = 0; i < array->count; i++) {
|
||||||
|
free_json_value(array->values[i]);
|
||||||
|
}
|
||||||
|
free(array->values);
|
||||||
|
free(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t inc_cursor(char **data, size_t *data_size, size_t amount)
|
||||||
|
{
|
||||||
|
size_t bytes_parsed = 0;
|
||||||
|
if (amount > *data_size) {
|
||||||
|
bytes_parsed = *data_size;
|
||||||
|
(*data) += *data_size;
|
||||||
|
(*data_size) = 0;
|
||||||
|
} else {
|
||||||
|
bytes_parsed = amount;
|
||||||
|
(*data) += amount;
|
||||||
|
(*data_size) -= amount;
|
||||||
|
}
|
||||||
|
return bytes_parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_whitespace(char c)
|
||||||
|
{
|
||||||
|
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t skip_ws(char **data, size_t *data_size)
|
||||||
|
{
|
||||||
|
size_t ws_count = 0;
|
||||||
|
while (data_size > 0) {
|
||||||
|
if (!is_whitespace(**data)) break;
|
||||||
|
inc_cursor(data, data_size, 1);
|
||||||
|
ws_count++;
|
||||||
|
}
|
||||||
|
return ws_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t get_json_string_size(char *data, size_t data_size)
|
||||||
|
{
|
||||||
|
if (data_size == 0) return -1;
|
||||||
|
|
||||||
|
assert(data[0] == '"');
|
||||||
|
for (int i = 1; i < data_size; i++) {
|
||||||
|
bool is_escaped = false;
|
||||||
|
if (i > 2) {
|
||||||
|
is_escaped = data[i-1] == '\\' && data[i-2] != '\\';
|
||||||
|
} else {
|
||||||
|
is_escaped = data[i-1] == '\\';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[i] == '"' && !is_escaped) return i+1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 decode_hex(char hex)
|
||||||
|
{
|
||||||
|
if ('a' <= hex && hex <= 'f') {
|
||||||
|
return hex - 'a' + 10;
|
||||||
|
} else if ('A' <= hex && hex <= 'F') {
|
||||||
|
return hex - 'A' + 10;
|
||||||
|
} else if ('0' <= hex && hex <= '9') {
|
||||||
|
return hex - '0';
|
||||||
|
} else {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char read_hex_symbol(char *data)
|
||||||
|
{
|
||||||
|
u8 byte1 = decode_hex(data[0]);
|
||||||
|
u8 byte2 = decode_hex(data[1]);
|
||||||
|
u8 byte3 = decode_hex(data[2]);
|
||||||
|
u8 byte4 = decode_hex(data[3]);
|
||||||
|
return (byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (byte4 << 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool expect_char(char *data, size_t data_size, char expected)
|
||||||
|
{
|
||||||
|
if (data_size == 0) return false;
|
||||||
|
return data[0] == expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_number_char(char c)
|
||||||
|
{
|
||||||
|
char number_chars[] = "0123456789.-";
|
||||||
|
for (size_t i = 0; i < ARRAY_LEN(number_chars)-1; i++) {
|
||||||
|
if (number_chars[i] == c) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool expect_number_char(char *data, size_t data_size)
|
||||||
|
{
|
||||||
|
if (data_size == 0) return false;
|
||||||
|
return is_number_char(data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_json_value(struct json_value *result, char *data, size_t data_size)
|
||||||
|
{
|
||||||
|
size_t bytes_parsed = 0;
|
||||||
|
bytes_parsed += skip_ws(&data, &data_size);
|
||||||
|
|
||||||
|
if (expect_char(data, data_size, '{')) {
|
||||||
|
result->type = JSON_TYPE_OBJECT;
|
||||||
|
result->object = malloc(sizeof(struct json_object));
|
||||||
|
int object_size = parse_json_object(result->object, data, data_size);
|
||||||
|
assert(object_size >= 0);
|
||||||
|
bytes_parsed += object_size;
|
||||||
|
} else if (expect_char(data, data_size, '[')) {
|
||||||
|
result->type = JSON_TYPE_ARRAY;
|
||||||
|
result->object = malloc(sizeof(struct json_array));
|
||||||
|
int array_size = parse_json_array(result->array, data, data_size);
|
||||||
|
assert(array_size >= 0);
|
||||||
|
bytes_parsed += array_size;
|
||||||
|
} else if (expect_number_char(data, data_size)) {
|
||||||
|
result->type = JSON_TYPE_NUMBER;
|
||||||
|
int number_size = parse_json_number(&result->number, data, data_size);
|
||||||
|
assert(number_size >= 0);
|
||||||
|
bytes_parsed += number_size;
|
||||||
|
} else {
|
||||||
|
// TODO: number, null, boolean, string
|
||||||
|
assert(false && "todo");
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes_parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_json_object(struct json_object *result, char *data, size_t data_size)
|
||||||
|
{
|
||||||
|
assert(expect_char(data, data_size, '{'));
|
||||||
|
size_t bytes_parsed = 0;
|
||||||
|
bytes_parsed += inc_cursor(&data, &data_size, 1);
|
||||||
|
bytes_parsed += skip_ws(&data, &data_size);
|
||||||
|
|
||||||
|
result->count = 0;
|
||||||
|
result->keys = NULL;
|
||||||
|
result->values = NULL;
|
||||||
|
|
||||||
|
if (expect_char(data, data_size, '}')) { // Empty object
|
||||||
|
return bytes_parsed + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (data_size > 0) {
|
||||||
|
int idx = result->count;
|
||||||
|
result->count++;
|
||||||
|
result->keys = realloc(result->keys, result->count*sizeof(struct json_string*));
|
||||||
|
result->values = realloc(result->values, result->count*sizeof(struct json_value*));
|
||||||
|
result->keys[idx] = malloc(sizeof(struct json_string));
|
||||||
|
result->values[idx] = malloc(sizeof(struct json_value));
|
||||||
|
|
||||||
|
int key_size = parse_json_string(result->keys[idx], data, data_size);
|
||||||
|
assert(key_size >= 0);
|
||||||
|
|
||||||
|
bytes_parsed += inc_cursor(&data, &data_size, key_size);
|
||||||
|
bytes_parsed += skip_ws(&data, &data_size);
|
||||||
|
assert(expect_char(data, data_size, ':'));
|
||||||
|
bytes_parsed += inc_cursor(&data, &data_size, 1);
|
||||||
|
bytes_parsed += skip_ws(&data, &data_size);
|
||||||
|
|
||||||
|
int value_size = parse_json_value(result->values[idx], data, data_size);
|
||||||
|
assert(value_size >= 0);
|
||||||
|
|
||||||
|
bytes_parsed += inc_cursor(&data, &data_size, value_size);
|
||||||
|
bytes_parsed += skip_ws(&data, &data_size);
|
||||||
|
|
||||||
|
if (!expect_char(data, data_size, ',')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_parsed += inc_cursor(&data, &data_size, 1);
|
||||||
|
bytes_parsed += skip_ws(&data, &data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(expect_char(data, data_size, '}'));
|
||||||
|
|
||||||
|
return bytes_parsed+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_json_array(struct json_array *result, char *data, size_t data_size)
|
||||||
|
{
|
||||||
|
assert(expect_char(data, data_size, '['));
|
||||||
|
size_t bytes_parsed = 0;
|
||||||
|
bytes_parsed += inc_cursor(&data, &data_size, 1);
|
||||||
|
bytes_parsed += skip_ws(&data, &data_size);
|
||||||
|
|
||||||
|
result->count = 0;
|
||||||
|
result->values = NULL;
|
||||||
|
|
||||||
|
if (expect_char(data, data_size, ']')) { // Empty object
|
||||||
|
return bytes_parsed + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (data_size > 0) {
|
||||||
|
int idx = result->count;
|
||||||
|
result->count++;
|
||||||
|
result->values = realloc(result->values, result->count*sizeof(struct json_value*));
|
||||||
|
result->values[idx] = malloc(sizeof(struct json_value));
|
||||||
|
|
||||||
|
int value_size = parse_json_value(result->values[idx], data, data_size);
|
||||||
|
assert(value_size >= 0);
|
||||||
|
|
||||||
|
bytes_parsed += inc_cursor(&data, &data_size, value_size);
|
||||||
|
bytes_parsed += skip_ws(&data, &data_size);
|
||||||
|
|
||||||
|
if (!expect_char(data, data_size, ',')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_parsed += inc_cursor(&data, &data_size, 1);
|
||||||
|
bytes_parsed += skip_ws(&data, &data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(expect_char(data, data_size, ']'));
|
||||||
|
|
||||||
|
return bytes_parsed+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_json_number(f64 *result, char *data, size_t data_size)
|
||||||
|
{
|
||||||
|
int bytes_parsed = 0;
|
||||||
|
for (int i = 0; i < data_size; i++) {
|
||||||
|
if (!is_number_char(data[i])) break;
|
||||||
|
bytes_parsed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: `strtod` until first non number character
|
||||||
|
// Use alternative parsing, so `data_size` can be enforced
|
||||||
|
*result = strtod(data, NULL);
|
||||||
|
|
||||||
|
return bytes_parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_json_string(struct json_string *result, char *data, size_t data_size)
|
||||||
|
{
|
||||||
|
assert(expect_char(data, data_size, '"'));
|
||||||
|
|
||||||
|
int json_string_size = get_json_string_size(data, data_size);
|
||||||
|
|
||||||
|
result->str = malloc((json_string_size-2)*sizeof(char));
|
||||||
|
result->size = 0;
|
||||||
|
|
||||||
|
for (size_t i = 1; i < data_size-1; i++) {
|
||||||
|
size_t str_idx = result->size;
|
||||||
|
if (data[i] == '"') {
|
||||||
|
break;
|
||||||
|
} else if (data[i] == '\\') {
|
||||||
|
if (data[i+1] == '\\') {
|
||||||
|
result->str[str_idx] = '\\';
|
||||||
|
} else if (data[i+1] == '"') {
|
||||||
|
result->str[str_idx] = '"';
|
||||||
|
} else if (data[i+1] == '/') {
|
||||||
|
result->str[str_idx] = '/';
|
||||||
|
} else if (data[i+1] == 'b') {
|
||||||
|
result->str[str_idx] = '\b';
|
||||||
|
} else if (data[i+1] == 'f') {
|
||||||
|
result->str[str_idx] = '\f';
|
||||||
|
} else if (data[i+1] == 'n') {
|
||||||
|
result->str[str_idx] = '\n';
|
||||||
|
} else if (data[i+1] == 'r') {
|
||||||
|
result->str[str_idx] = '\r';
|
||||||
|
} else if (data[i+1] == 't') {
|
||||||
|
result->str[str_idx] = '\t';
|
||||||
|
} else if (data[i+1] == 'u') {
|
||||||
|
result->str[str_idx] = read_hex_symbol(data + i + 2);
|
||||||
|
i+=4;
|
||||||
|
} else {
|
||||||
|
abort(); // Unknown escape sequence
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
result->str[str_idx] = data[i];
|
||||||
|
}
|
||||||
|
result->size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
result->str = realloc(result->str, result->size*sizeof(char));
|
||||||
|
|
||||||
|
return json_string_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_json_null(char *data, size_t data_size)
|
||||||
|
{
|
||||||
|
return 0; // TODO:
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_json_bool(bool *result, char *data, size_t data_size)
|
||||||
|
{
|
||||||
|
return 0; // TODO:
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void printf_json_array(struct json_array *array)
|
||||||
|
{
|
||||||
|
printf("[");
|
||||||
|
for (int i = 0; i < array->count; i++) {
|
||||||
|
printf_json_value(array->values[i]);
|
||||||
|
if (i < array->count-1) printf(",");
|
||||||
|
}
|
||||||
|
printf("]");
|
||||||
|
}
|
||||||
|
|
||||||
|
void printf_json_object(struct json_object *object)
|
||||||
|
{
|
||||||
|
printf("{");
|
||||||
|
for (int i = 0; i < object->count; i++) {
|
||||||
|
printf_json_string(object->keys[i]);
|
||||||
|
printf(":");
|
||||||
|
printf_json_value(object->values[i]);
|
||||||
|
if (i < object->count-1) printf(",");
|
||||||
|
}
|
||||||
|
printf("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void printf_json_string(struct json_string *string)
|
||||||
|
{
|
||||||
|
// TODO: Support to display escaped sequences
|
||||||
|
printf("\"%.*s\"", (int)string->size, string->str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printf_json_value(struct json_value *value)
|
||||||
|
{
|
||||||
|
switch(value->type) {
|
||||||
|
case JSON_TYPE_NULL:
|
||||||
|
printf("null");
|
||||||
|
break;
|
||||||
|
case JSON_TYPE_OBJECT:
|
||||||
|
printf_json_object(value->object);
|
||||||
|
break;
|
||||||
|
case JSON_TYPE_ARRAY:
|
||||||
|
printf_json_array(value->array);
|
||||||
|
break;
|
||||||
|
case JSON_TYPE_STRING:
|
||||||
|
printf_json_string(value->string);
|
||||||
|
break;
|
||||||
|
case JSON_TYPE_NUMBER:
|
||||||
|
printf("%.16f", value->number);
|
||||||
|
break;
|
||||||
|
case JSON_TYPE_BOOLEAN:
|
||||||
|
printf("%s", value->boolean ? "true" : "false");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
58
src/json_parser.h
Normal file
58
src/json_parser.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
enum json_type {
|
||||||
|
JSON_TYPE_NULL,
|
||||||
|
JSON_TYPE_OBJECT,
|
||||||
|
JSON_TYPE_ARRAY,
|
||||||
|
JSON_TYPE_STRING,
|
||||||
|
JSON_TYPE_NUMBER,
|
||||||
|
JSON_TYPE_BOOLEAN,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_string {
|
||||||
|
char *str;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_array {
|
||||||
|
struct json_value **values;
|
||||||
|
size_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_object {
|
||||||
|
struct json_string **keys;
|
||||||
|
struct json_value **values;
|
||||||
|
size_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_value {
|
||||||
|
enum json_type type;
|
||||||
|
union {
|
||||||
|
bool boolean;
|
||||||
|
f64 number;
|
||||||
|
struct json_string *string;
|
||||||
|
struct json_array *array;
|
||||||
|
struct json_object *object;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
void free_json_value(struct json_value *value);
|
||||||
|
void free_json_array(struct json_array *array);
|
||||||
|
void free_json_string(struct json_string *string);
|
||||||
|
void free_json_object(struct json_object *object);
|
||||||
|
|
||||||
|
int parse_json_null(char *data, size_t data_size);
|
||||||
|
int parse_json_bool(bool *result, char *data, size_t data_size);
|
||||||
|
int parse_json_number(f64 *result, char *data, size_t data_size);
|
||||||
|
int parse_json_value(struct json_value *result, char *data, size_t data_size);
|
||||||
|
int parse_json_object(struct json_object *result, char *data, size_t data_size);
|
||||||
|
int parse_json_array(struct json_array *result, char *data, size_t data_size);
|
||||||
|
int parse_json_string(struct json_string *result, char *data, size_t data_size);
|
||||||
|
|
||||||
|
void printf_json_value(struct json_value *result);
|
||||||
|
void printf_json_string(struct json_string *string);
|
||||||
|
void printf_json_object(struct json_object *object);
|
||||||
|
void printf_json_array(struct json_array *array);
|
53
src/main.c
Normal file
53
src/main.c
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "json_parser.c"
|
||||||
|
|
||||||
|
void print_usage(char *program)
|
||||||
|
{
|
||||||
|
printf("Usage: %s <json-file> [test-bin-file]\n", program);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t get_file_size(FILE *f)
|
||||||
|
{
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
size_t size = ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *json_filename = argv[1];
|
||||||
|
|
||||||
|
FILE *f = fopen(json_filename, "r");
|
||||||
|
if (f == NULL) {
|
||||||
|
printf("Failed to open: %s\n", json_filename);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t json_size = get_file_size(f);
|
||||||
|
char json_data[json_size];
|
||||||
|
size_t bytes_read = fread(json_data, 1, json_size, f);
|
||||||
|
if (bytes_read != json_size) {
|
||||||
|
printf("Failed to read all contents of file\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
struct json_value *parsed = malloc(sizeof(struct json_value));
|
||||||
|
int bytes_parsed = parse_json_value(parsed, json_data, json_size);
|
||||||
|
|
||||||
|
printf_json_value(parsed);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
free_json_value(parsed);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user