i can't believe i just wrote a json parser in C

This commit is contained in:
ari melody 2024-05-19 23:13:32 +01:00
commit 22e60ee228
Signed by: ari
GPG key ID: CF99829C92678188
7 changed files with 537 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
bin/

10
README.md Normal file
View 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
View file

@ -0,0 +1,3 @@
mkdir -p ./bin/
gcc -Wall -o ./bin/compile ./src/*.c ./src/parsers/*.c

108
src/main.c Normal file
View 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
View 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
View 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
View 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"
}
]
}