i can't believe i just wrote a json parser in C
This commit is contained in:
commit
22e60ee228
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
bin/
|
10
README.md
Normal file
10
README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# arimelody.me site generator
|
||||
|
||||
i use-a the json to make-a the html 🤌 🇮🇹
|
||||
|
||||
## how to use
|
||||
|
||||
- run the `build.sh` script to uh. build the thing
|
||||
- run `./bin/compile -t music -i ./test.json`
|
||||
- ???
|
||||
- profit
|
3
build.sh
Executable file
3
build.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
mkdir -p ./bin/
|
||||
|
||||
gcc -Wall -o ./bin/compile ./src/*.c ./src/parsers/*.c
|
108
src/main.c
Normal file
108
src/main.c
Normal file
|
@ -0,0 +1,108 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "parsers/json.h"
|
||||
|
||||
typedef enum {
|
||||
music = 0,
|
||||
art = 1,
|
||||
blog = 2,
|
||||
} MediaType;
|
||||
|
||||
MediaType read_media_type(char *name) {
|
||||
char *type_names[] = {"music", "art", "blog"};
|
||||
for (int i = 0; i < sizeof(type_names) / sizeof(char*); i++) {
|
||||
if (strcmp(name, type_names[i]) == 0) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void print_help() {
|
||||
printf("usage: compile <-t media_type> <-i input_file> [output_file]\n");
|
||||
printf("valid media types: music, art, blog\n");
|
||||
printf("if output_file is not specified, the output will be written to stdout.\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc <= 1) {
|
||||
print_help();
|
||||
return 0;
|
||||
}
|
||||
|
||||
MediaType media_type = -1;
|
||||
FILE *input_file = NULL;
|
||||
size_t optind = 0;
|
||||
|
||||
for (optind = 1; optind < argc && argv[optind][0] == '-'; optind++) {
|
||||
switch (argv[optind][1]) {
|
||||
case 't':
|
||||
media_type = read_media_type(argv[++optind]);
|
||||
if (media_type < 0) {
|
||||
fprintf(stderr, "media type `%s` is not implemented!\n\n", argv[optind]);
|
||||
print_help();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'i':
|
||||
if (optind++ >= argc) {
|
||||
fprintf(stderr, "-i was passed, but no file path was given!\n\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
input_file = fopen(argv[optind], "r");
|
||||
if (input_file == NULL) {
|
||||
fprintf(stderr, "failed to open file %s!\n\n", argv[optind]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print_help();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (media_type < 0) {
|
||||
fprintf(stderr, "media type not provided!\n\n");
|
||||
print_help();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (input_file == NULL) {
|
||||
fprintf(stderr, "input file not provided!\n\n");
|
||||
print_help();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// get length of file
|
||||
fseek(input_file, -sizeof(char), SEEK_END);
|
||||
size_t length = ftell(input_file);
|
||||
fseek(input_file, 0, SEEK_SET);
|
||||
|
||||
// read file to buffer
|
||||
char buf[length + 1];
|
||||
fread(buf, 1, length, input_file);
|
||||
buf[length] = '\0';
|
||||
|
||||
size_t offset = 0;
|
||||
JsonObject *json = json_parse(buf, &offset);
|
||||
if (json == NULL) {
|
||||
fprintf(stderr, "failed to parse input file!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("title: %s\n", (char*)json_get(json, "title"));
|
||||
printf("type: %s\n", (char*)json_get(json, "type"));
|
||||
printf("year: %.0f\n", *(float*)json_get(json, "year"));
|
||||
printf("artwork: %s\n", (char*)json_get(json, "artwork"));
|
||||
printf("buylink: %s\n", (char*)json_get(json, "buylink"));
|
||||
|
||||
JsonArrayItem *links = json_get(json, "links");
|
||||
size_t i = 0;
|
||||
while (links != NULL) {
|
||||
JsonObject *link = links->value;
|
||||
printf("links.%zu.title: %s\n", i, (char*)json_get(link, "title"));
|
||||
printf("links.%zu.url: %s\n", i, (char*)json_get(link, "url"));
|
||||
links = links->next;
|
||||
i++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
359
src/parsers/json.c
Normal file
359
src/parsers/json.c
Normal file
|
@ -0,0 +1,359 @@
|
|||
#include "json.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#define SKIP_WHITESPACE while ((data[i] == ' ' || data[i] == '\t' || data[i] == '\n') && data[i] != '\0') i++
|
||||
|
||||
void json_log(const char *error, char *data, size_t index) {
|
||||
int cur = 0;
|
||||
int line_num = 0;
|
||||
int line_pos = 0;
|
||||
|
||||
// get line number and position
|
||||
while (cur < index) {
|
||||
line_pos++;
|
||||
if (data[cur] == '\n') {
|
||||
line_pos = 0;
|
||||
line_num++;
|
||||
}
|
||||
cur++;
|
||||
}
|
||||
|
||||
// get line length
|
||||
int line_len = line_pos;
|
||||
while (data[cur] != '\0' && data[cur] != '\n') {
|
||||
line_len++;
|
||||
cur++;
|
||||
}
|
||||
|
||||
// get line
|
||||
char *line = malloc(sizeof(char) * line_len);
|
||||
memcpy(line, data + (index - line_pos) * sizeof(char), line_len * sizeof(char));
|
||||
|
||||
fprintf(stderr, "%s\n", error);
|
||||
fprintf(stderr, "\tat %d:%d:\n", line_num + 1, line_pos + 1);
|
||||
fprintf(stderr, "%s\n", line);
|
||||
for (int i = 0; i < line_pos; i++) fprintf(stderr, " ");
|
||||
fprintf(stderr, "^\n\n");
|
||||
}
|
||||
|
||||
JsonArrayItem *parse_array(char *data, size_t *offset);
|
||||
void *parse_value(JsonType *type, char *data, size_t *offset);
|
||||
|
||||
JsonArrayItem *parse_array(char *data, size_t *offset) {
|
||||
size_t i = *offset + 1;
|
||||
size_t count = 0;
|
||||
JsonArrayItem *head = NULL;
|
||||
while (data[i] != '\0' && data[i] != ']') {
|
||||
void *item_value = NULL;
|
||||
JsonType type = -1;
|
||||
|
||||
item_value = parse_value(&type, data, &i);
|
||||
if (item_value == NULL) return NULL;
|
||||
|
||||
if (type == object) {
|
||||
JsonObject *obj = item_value;
|
||||
|
||||
if (obj->name != NULL) free(obj->name);
|
||||
obj->name = malloc(sizeof(char) * log10(count) + 1);
|
||||
sprintf(obj->name, "%zu", count);
|
||||
}
|
||||
|
||||
if (head == NULL) {
|
||||
head = malloc(sizeof(JsonArrayItem));
|
||||
((JsonArrayItem*)head)->value = item_value;
|
||||
} else {
|
||||
JsonArrayItem *last = head;
|
||||
while (last->next != NULL) last = last->next;
|
||||
last->next = malloc(sizeof(JsonArrayItem));
|
||||
last->next->value = item_value;
|
||||
}
|
||||
|
||||
SKIP_WHITESPACE;
|
||||
// skip comma, if there is one
|
||||
if (data[i] == ',') i++;
|
||||
}
|
||||
|
||||
if (data[i] == '\0') {
|
||||
json_log("error: array was not closed", data, *offset);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*offset = i + 1;
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void *parse_value(JsonType *type, char *data, size_t *offset) {
|
||||
size_t i = *offset;
|
||||
*type = -1;
|
||||
|
||||
// infer type from first character
|
||||
SKIP_WHITESPACE;
|
||||
switch (data[i]) {
|
||||
case '{': *type = object; break;
|
||||
case '[': *type = array; break;
|
||||
case '"': *type = string; break;
|
||||
case '0': case '1': case '2':
|
||||
case '3': case '4': case '5':
|
||||
case '6': case '7': case '8':
|
||||
case '9': *type = number; break;
|
||||
default: {
|
||||
json_log("error: malformed json value", data, i);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void *value = NULL;
|
||||
switch (*type) {
|
||||
case object:
|
||||
{
|
||||
value = json_parse(data, &i);
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
case array:
|
||||
{
|
||||
value = parse_array(data, &i);
|
||||
break;
|
||||
}
|
||||
case number:
|
||||
{
|
||||
float number = 0.f;
|
||||
uint64_t decimals = 0;
|
||||
while ((data[i] >= '0' && data[i] <= '9') || data[i] == '.') {
|
||||
// check for decimal point
|
||||
if (data[i] == '.') {
|
||||
if (decimals != 0) {
|
||||
// two decimal points in one number??
|
||||
json_log("error: malformed number", data, *offset);
|
||||
return NULL;
|
||||
}
|
||||
decimals = 1;
|
||||
}
|
||||
|
||||
if (decimals <= 0)
|
||||
number = (number * 10) + (data[i] - '0');
|
||||
else
|
||||
number += (data[i] - '0') * (10 * decimals);
|
||||
|
||||
i++;
|
||||
}
|
||||
value = malloc(sizeof(float));
|
||||
*(float*)value = number;
|
||||
|
||||
break;
|
||||
}
|
||||
case string:
|
||||
{
|
||||
// skip first quote
|
||||
i++;
|
||||
// lookahead to get name length
|
||||
size_t length = 0;
|
||||
while (data[i + length] != '\0' && data[i + length] != '"') {
|
||||
length++;
|
||||
}
|
||||
|
||||
// error if string was never closed
|
||||
if (data[i + length] == '\0') {
|
||||
json_log("error: malformed json", data, *offset);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// consume string
|
||||
value = malloc(sizeof(char) * (length + 1));
|
||||
memcpy(value, data + sizeof(char) * i, sizeof(char) * length);
|
||||
((char*)value)[length] = '\0';
|
||||
|
||||
// skip over string
|
||||
i += length + 1;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*offset = i;
|
||||
return value;
|
||||
}
|
||||
|
||||
JsonObject *json_parse(char *data, size_t *offset) {
|
||||
if (data[*offset] != '{') {
|
||||
json_log("error: malformed json object", data, *offset);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t i = *offset + 1;
|
||||
JsonObject *root = malloc(sizeof(JsonObject));
|
||||
root->type = object;
|
||||
JsonObject *last_child = NULL;
|
||||
|
||||
while (data[i] != '\0' && data[i] != '}') {
|
||||
// read until a quote is found (name start)
|
||||
if (data[i++] != '"') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// lookahead to get name length
|
||||
size_t name_length = 0;
|
||||
while (data[i + name_length] != '\0' && data[i + name_length] != '"') {
|
||||
name_length++;
|
||||
}
|
||||
|
||||
// error if string was never closed
|
||||
if (data[i + name_length] == '\0') {
|
||||
json_log("error: malformed json", data, i);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// consume string as object name
|
||||
char *name = malloc(sizeof(char) * (name_length + 1));
|
||||
memcpy(name, data + sizeof(char) * i, sizeof(char) * name_length);
|
||||
name[name_length] = '\0';
|
||||
|
||||
// skip over object name
|
||||
i += name_length + 1;
|
||||
|
||||
SKIP_WHITESPACE;
|
||||
// next character must be ':'
|
||||
if (data[i] != ':') {
|
||||
json_log("error: ':' does not succeed attribute definition", data, i);
|
||||
free(name);
|
||||
return NULL;
|
||||
}
|
||||
i++;
|
||||
|
||||
JsonType type;
|
||||
void *value = parse_value(&type, data, &i);
|
||||
|
||||
if (value == NULL) {
|
||||
free(name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JsonObject *obj;
|
||||
if (type == object) {
|
||||
obj = json_parse(data, &i);
|
||||
} else {
|
||||
// create new json object
|
||||
obj = malloc(sizeof(JsonObject));
|
||||
obj->name = name;
|
||||
obj->type = type;
|
||||
obj->value = value;
|
||||
}
|
||||
|
||||
// append new object to root's children
|
||||
if (last_child != NULL) {
|
||||
last_child->next = obj;
|
||||
last_child = obj;
|
||||
} else {
|
||||
root->children = obj;
|
||||
last_child = obj;
|
||||
}
|
||||
|
||||
SKIP_WHITESPACE;
|
||||
// skip comma, if there is one
|
||||
if (data[i + 1] == '\0' && data[i + 1] == ',') i++;
|
||||
}
|
||||
|
||||
if (data[i] != '}') {
|
||||
json_log("error: json object not closed", data, i);
|
||||
json_free(root);
|
||||
root = NULL;
|
||||
}
|
||||
|
||||
*offset = i;
|
||||
return root;
|
||||
}
|
||||
|
||||
void *json_get(JsonObject *obj, const char *query) {
|
||||
if (obj == NULL) return NULL;
|
||||
|
||||
size_t cur = 0;
|
||||
JsonType searching_type = object;
|
||||
|
||||
while (query[cur] != '\0') {
|
||||
size_t attr_len = 0;
|
||||
while (query[cur + attr_len] != '\0' && query[cur + attr_len] != '.') attr_len++;
|
||||
char attr[attr_len];
|
||||
memcpy(attr, query + cur * sizeof(char), attr_len * sizeof(char));
|
||||
|
||||
if (searching_type == object) {
|
||||
obj = obj->children;
|
||||
while (obj != NULL) {
|
||||
if (strcmp(obj->name, attr) == 0) {
|
||||
break;
|
||||
}
|
||||
obj = obj->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (searching_type == array) {
|
||||
size_t to_i = 0;
|
||||
size_t cur = 0;
|
||||
while (cur < attr_len) {
|
||||
if (attr[cur] < '0' || attr[cur] > '9') {
|
||||
fprintf(stderr, "error: attempt to index array with attribute name (%s)\n", attr);
|
||||
return NULL;
|
||||
}
|
||||
to_i = (to_i * 10) + (attr[cur] - '0');
|
||||
cur++;
|
||||
}
|
||||
|
||||
JsonArrayItem *item = obj->value;
|
||||
size_t i = 0;
|
||||
while (item != NULL && i < to_i) {
|
||||
item = item->next;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (item == NULL) {
|
||||
fprintf(stderr, "error: index %zu out of bounds for array %s\n", to_i, attr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
obj = item->value;
|
||||
}
|
||||
|
||||
if (obj == NULL) {
|
||||
fprintf(stderr, "error: %s does not exist in json object\n", query);
|
||||
return NULL;
|
||||
}
|
||||
cur += attr_len;
|
||||
if (query[cur] == '.') {
|
||||
searching_type = obj->type;
|
||||
cur++;
|
||||
}
|
||||
}
|
||||
|
||||
return obj->value;
|
||||
}
|
||||
|
||||
void json_free(JsonObject *object) {
|
||||
if (object == NULL) return;
|
||||
|
||||
if (object->children != NULL) {
|
||||
json_free(object->children);
|
||||
}
|
||||
if (object->next != NULL) {
|
||||
json_free(object->next);
|
||||
}
|
||||
|
||||
if (object->name != NULL) free(object->name);
|
||||
if (object->value != NULL) {
|
||||
if (object->type == array) {
|
||||
while (object->value != NULL) {
|
||||
JsonArrayItem *old = object->value;
|
||||
json_free(old->value);
|
||||
object->value = old->next;
|
||||
free(old);
|
||||
}
|
||||
}
|
||||
free(object->value);
|
||||
}
|
||||
|
||||
free(object);
|
||||
}
|
31
src/parsers/json.h
Normal file
31
src/parsers/json.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
#ifndef _def_json_parser
|
||||
#define _def_json_parser
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef enum {
|
||||
object = 0,
|
||||
array = 1,
|
||||
number = 2,
|
||||
string = 3,
|
||||
} JsonType;
|
||||
|
||||
typedef struct JsonObject {
|
||||
char *name;
|
||||
JsonType type;
|
||||
void *value;
|
||||
|
||||
struct JsonObject *children;
|
||||
struct JsonObject *next;
|
||||
} JsonObject;
|
||||
|
||||
typedef struct JsonArrayItem {
|
||||
JsonObject *value;
|
||||
struct JsonArrayItem *next;
|
||||
} JsonArrayItem;
|
||||
|
||||
JsonObject *json_parse(char *data, size_t *offset);
|
||||
void *json_get(JsonObject *object, const char *child_name);
|
||||
void json_free(JsonObject *object);
|
||||
|
||||
#endif // _def_json_parser
|
25
test.json
Normal file
25
test.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"title": "Dream",
|
||||
"type": "single",
|
||||
"year": 2022,
|
||||
"artwork": "https://mellodoot.com/img/music_artwork/mellodoot_-_Dream.webp",
|
||||
"buylink": "https://arimelody.bandcamp.com/track/dream",
|
||||
"links": [
|
||||
{
|
||||
"title": "Spotify",
|
||||
"url": "https://open.spotify.com/album/5talRpqzjExP1w6j5LFIAh"
|
||||
},
|
||||
{
|
||||
"title": "Apple Music",
|
||||
"url": "https://music.apple.com/ie/album/dream-single/1650037132"
|
||||
},
|
||||
{
|
||||
"title": "Soundcloud",
|
||||
"url": "https://soundcloud.com/mellodoot/dream"
|
||||
},
|
||||
{
|
||||
"title": "YouTube",
|
||||
"url": "https://www.youtube.com/watch?v=nfFgtMuYAx8"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue