For the past few weeks, I've been using the wonderful cJSON to add content to my game. But recently, I got tired of JSON's verbosity so I decided to roll my own format called SON (Simple Object Notation). I plan on using this format in all of my future game projects.
rogue
anim
timer 0
brain
type player
image
visibility visible
texture rogue
imagebox 0 0 24 40
imageoff 0 -20
player
pos
loc -30 85
dir south
solidity solid
kobold
anim
timer 0
brain
type monster
image
visibility visible
texture kobold
imagebox 0 0 24 40
imageoff 0 -20
pos
loc 12 45
dir south
solidity solid
My parser is closely modeled after cJSON. I still have work to do (search by key, file writing, etc) but I'd like to get feedback on what I have so far. The three things I'm concerned about are:
- How is my coding style? I've only been using C for a few weeks and I'm still trying to find what works for me. I used to write C++ capitalization in Allman but I've recently switched to C-style capitalization and 1TBS.
- Is my file parsing efficient? This is my first time reading files in C and I struggled for several days figuring out the recursion part.
- How would approach writing my SON tree back to a file? What algorithms do I need to learn? I want my output to look like the input character-for-character.
sonparse.h
#pragma once
/* A node in a son file with accompanying value and relatives */
typedef struct son son;
struct son {
char* key;
son* child;
son* next;
enum { SON_NONE, SON_INTEGER, SON_STRING, SON_ARRAY } type;
union {
int integer;
char* string;
struct {
int* data;
int size;
} array;
};
};
/* Create a new node and set pointers to NULL. */
son* son_new(void);
/* Recursively free a node and all of its relatives. */
void son_del(son* node);
/* Parse a son tree from the given file path. */
son* son_parse(const char* filepath);
/* Print the node to console for debugging. */
void son_print(son* node);
sonparse.c
#include "sonparse.h"
#include <stdio.h>
#include <stdlib.h>
/* The file being parsed */
static FILE* file;
/* The character at the current file position. */
static char c;
/* Temporary node being returned to a higher recursion level. */
static son* temp_node;
/* The depth of the temporary node. */
static int temp_tab;
/* Read key at current file position into the node. */
static void read_key(son* node);
/* Read value at current file position into node. */
static void read_value(son* node);
/* Read array at current file position into node. */
static void read_array(son* node);
/* Read string at current file position into node. */
static void read_string(son* node);
/* Build a tree off of a node, setting pointers. */
static void build_tree(int this_tab, son* this_node);
son* son_new(void) {
son* node = malloc(sizeof(son));
node->child = NULL;
node->next = NULL;
return node;
}
void son_del(son* node) {
if (node->type == SON_STRING) {
free(node->string);
} else if (node->type == SON_ARRAY) {
free(node->array.data);
}
if (node->child) {
son_del(node->child);
}
if (node->next) {
son_del(node->next);
}
free(node->key);
free(node);
}
son* son_parse(const char* filepath) {
/*
Example usage:
son* tree = son_parse("entity.son");
son_print(tree->next->child->next->next->child->next->next);
son_del(tree);
Output:
imagebox : 0 0 24 40
next: imageoff child: (null)
*/
file = fopen(filepath, "r");
son* root = son_new();
read_key(root);
read_value(root);
build_tree(0, root);
return root;
}
void son_print(son* node) {
printf("%s", node->key);
/* Print node value, or (null) if none. */
char* valuestr = NULL;
switch (node->type) {
case SON_INTEGER:
printf(" : %d\n", node->integer);
break;
case SON_ARRAY:
printf(" :");
for(int i = 0; i < node->array.size; ++i) {
printf(" %d", node->array.data[i]);
}
putchar('\n');
break;
case SON_STRING:
valuestr = node->string;
case SON_NONE:
default:
printf(" : %s\n", valuestr);
break;
}
/* Print node siblings, or (null) if none. */
char* nextstr = NULL;
char* childstr = NULL;
if (node->next) {
nextstr = node->next->key;
}
if (node->child) {
childstr = node->child->key;
}
printf("next: %s child: %s\n", nextstr, childstr);
}
void read_key(son* node) {
/* Read until a space or newline to get key length. */
int key_start = ftell(file);
int key_size = 0;
c = getc(file);
while(c && c != ' ' && c != '\n') {
++key_size;
c = getc(file);
}
fseek(file, key_start, SEEK_SET);
/* Allocate space for node key and null-terminator. */
++key_size;
node->key = malloc(sizeof(char) * key_size);
fread(node->key, sizeof(char), key_size, file);
node->key[--key_size] = '\0';
}
void read_value(son* node) {
/* Return if there is no value after key. */
if (c != ' ') {
node->type = SON_NONE;
return;
}
c = getc(file);
if (c == '-' || (c >= '0' && c <= '9')) {
read_array(node);
} else {
read_string(node);
}
}
void read_array(son* node) {
int array_start = ftell(file) - 1;
int array_size = 1;
while (c && c != '\n') {
if (c == ' ') {
++array_size;
}
c = getc(file);
}
fseek(file, array_start, SEEK_SET);
if (array_size == 1) {
node->type = SON_INTEGER;
node->integer = 0;
int sign = 1;
c = getc(file);
while (c && c != '\n') {
if (c == '-') {
sign = -1;
} else {
/* Add digit to integer */
node->integer *= 10;
node->integer += (c - '0') * sign;
}
c = getc(file);
}
} else {
node->type = SON_ARRAY;
node->array.size = array_size;
node->array.data = malloc(sizeof(int) * array_size);
node->array.data[0] = 0;
int sign = 1;
int i = 0;
c = getc(file);
while (c && c != '\n') {
if (c == '-') {
sign = -1;
} else if (c == ' ') {
++i;
sign = 1;
node->array.data[i] = 0;
} else {
/* Add digit to current index. */
node->array.data[i] *= 10;
node->array.data[i] += (c - '0') * sign;
}
c = getc(file);
}
}
}
void read_string(son* node) {
node->type = SON_STRING;
int str_start = ftell(file) - 1;
int str_size = 0;
while(c != EOF && c != '\n') {
++str_size;
c = getc(file);
}
fseek(file, str_start, SEEK_SET);
/* Allocate space for node string and null-terminator. */
++str_size;
node->string = malloc(sizeof(char) * str_size);
fread(node->string, sizeof(char), str_size, file);
node->string[--str_size] = '\0';
}
void build_tree(int this_tab, son* this_node) {
/* Reached end of file, finish parsing. */
if (c == EOF) {
return;
}
/* Determine depth of node on next line. */
int next_tab = 0;
c = getc(file);
while (c == '\t') {
++next_tab;
c = getc(file);
}
ungetc(c, file);
/* Create next node, determine if child or adjacent. */
son* next_node = son_new();
read_key(next_node);
read_value(next_node);
if (next_tab > this_tab) {
this_node->child = next_node;
build_tree(next_tab, next_node);
/* Check if a node was returned to this depth. */
if (temp_node && temp_tab == this_tab) {
this_node->next = temp_node;
temp_node = NULL;
build_tree(temp_tab, this_node->next);
}
} else if (next_tab == this_tab) {
this_node->next = next_node;
build_tree(next_tab, next_node);
} else {
/* Return next node to previous depth. */
temp_node = next_node;
temp_tab = next_tab;
}
}