Skip to content

Instantly share code, notes, and snippets.

@a-voel
Last active February 22, 2022 13:39
Show Gist options
  • Save a-voel/913f246b2041c5685d564304314ec65b to your computer and use it in GitHub Desktop.
Save a-voel/913f246b2041c5685d564304314ec65b to your computer and use it in GitHub Desktop.
#ifndef AVCFG_H_INCLUDED
#define AVCFG_H_INCLUDED
/*****************************************************************************
* Declaration *
*****************************************************************************/
/* Size of internal hashtable for dependency resolution. If you have really big
configuration files you might increase this to improve performance. In this case
it might be time to reevaluate if this library is right for you anyway! */
#ifndef AVCFG_HASHSIZE
#define AVCFG_HASHSIZE 128
#endif
/* Delegate function you can overwrite if you want. You might leave some or the whole struct NULL
and (reasonable) default implementation will be used for these */
typedef struct avcfg_delegate{
/* Called on an (inrecoverable) error with a message for the user. This function should never return.
If you really want to continue your program you might escape with something like longjmp, but
this wíll leak a few kilobytes of memory and you might want to use a library with more robust error
handling anyway */
void (*onerror) (const char* message, struct avcfg_delegate delegate);
/* Load a file with configuration from disk or whereever it is stored. The resulting string NEEDS to
be zero-terminated.
You can use this if you want to lookup a file in multiple directorys. This would be a good idea
if you have for example a directory with base configuration files */
char* (*loadfile) (const char* filename, struct avcfg_delegate delegate);
/* Looks up a constants symbol. If the constanted called "name" is found its value must be written to
"value" and you have to return 1. If it is not found just return 0.
Might be some universal constant like pi or something only known by the runtime of your
application. */
int (*lookupsymbol) (const char* name, double* value, struct avcfg_delegate delegate);
/* Handle a call to a function called "name" with a single parameter "param". If this function is found
its value must be written to "value" and you have to return 1. If it is not found just return 0.
An example of this might be sin(x) */
int (*evaluatefunction)(const char* name, double param, double* value, struct avcfg_delegate delegate);
void* userdata;
} avcfg_delegate;
typedef struct{
double* values;
char** keys;
avcfg_delegate delegate;
} avcfg_config;
avcfg_config* avcfg_load (const char* filename, avcfg_delegate* delegate);
void avcfg_free (avcfg_config* config);
double avcfg_getf (avcfg_config*, const char* name);
double avcfg_getf_op(avcfg_config*, const char* name, double opt);
long long avcfg_geti (avcfg_config*, const char* name);
long long avcfg_geti_op(avcfg_config*, const char* name, long long opt);
/*****************************************************************************
* Implementation *
*****************************************************************************/
#ifdef AFPCFG_IMPL
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
/* tags for dependency sort */
#define AVCFGI_UNUSED 0
#define AVCFGI_INFLIGHT 1
#define AVCFGI_DONE 2
typedef struct avcfgi_symbol{
char* key;
char* value;
double finalvalue;
/* dependency sorting */
int status;
/* errormessages */
int linenumber;
char* filename;
/* hash bucket list */
struct avcfgi_symbol *next;
} avcfgi_symbol;
/* use open hashing for simple implementation */
typedef avcfgi_symbol** avcfgi_hashtable;
/* Utils */
avcfgi_symbol* avcfgi_symbolbyname(const char* name, avcfgi_hashtable list);
void avcfgi_addsymbol (avcfgi_symbol* symbol, avcfgi_hashtable list);
char* avcfgi_strdup (const char* c);
#ifdef _MSC_VER
__declspec(noreturn) void avcfgi_fatal(const char* msg, avcfg_delegate delegate);
#elif defined(__GNUC__)
void avcfgi_fatal(const char* msg, avcfg_delegate delegate) __attribute__((noreturn));
#else
void avcfgi_fatal(const char* msg, avcfg_delegate delegate);
#endif
/* main loading */
void avcfgi_installdefaultdelegate(avcfg_delegate* delegate);
void avcfgi_loadfile_rec (const char* path, avcfg_delegate delegate, avcfgi_hashtable hash);
void avcfgi_resolvesymbol (avcfgi_symbol *symbol, avcfg_delegate delegate, avcfgi_hashtable hash);
avcfg_config* avcfg_load(const char* path, avcfg_delegate *delegate){
/* Prepare delegate */
avcfg_delegate realdelegate;
if (delegate){
realdelegate = *delegate;
}else{
memset(&realdelegate, 0, sizeof(avcfg_delegate));
}
avcfgi_installdefaultdelegate(&realdelegate);
/* Load from "disk" */
avcfgi_hashtable hash = (avcfgi_symbol**)malloc(AVCFG_HASHSIZE*sizeof(avcfgi_symbol*));
memset(hash, 0, AVCFG_HASHSIZE*sizeof(avcfgi_symbol*));
avcfgi_loadfile_rec(path, realdelegate, hash);
/* dependency sorting */
int numsymbols = 0;
for(int i = 0; i != AVCFG_HASHSIZE; i++){
avcfgi_symbol *first = hash[i];
while(first){
numsymbols++;
if (first->status != AVCFGI_DONE){
avcfgi_resolvesymbol(first, realdelegate, hash);
}
first = first->next;
}
}
/* output generation and cleanup */
avcfg_config * cfg = (avcfg_config*)malloc(sizeof(avcfg_config));
cfg->keys = (char**)malloc(sizeof(char*)*(numsymbols+1));
cfg->values = (double*)malloc(sizeof(double)*(numsymbols+1));
cfg->delegate = realdelegate;
cfg->keys[numsymbols] = NULL;
int i = 0;
for(int j = 0; j != AVCFG_HASHSIZE; j++){
avcfgi_symbol* first = hash[j];
while(first){
cfg->keys[i] = first->key;
cfg->values[i] = first->finalvalue;
i++;
avcfgi_symbol* tmp = first->next;
free(first->value);
free(first->filename);
free(first);
first = tmp;
}
}
free(hash);
return cfg;
}
void avcfg_free(avcfg_config* config){
free(config->keys);
free(config->values);
free(config);
}
/* accessors */
int avcfgi_getconfigindex(avcfg_config*, const char* name);
double avcfg_getf(avcfg_config* config, const char* name){
int idx = avcfgi_getconfigindex(config, name);
if (idx < 0){
char err[128];
printf("1\n");
snprintf(err, 127, "AVCFG: Unknown variable %s\n", name);
avcfgi_fatal(err, config->delegate);
}
return config->values[idx];
}
double avcfg_getf_op(avcfg_config* config, const char* name, double opt){
int idx = avcfgi_getconfigindex(config, name);
if (idx < 0){
return opt;
}
return config->values[idx];
}
long long avcfg_geti(avcfg_config* config, const char* name){
int idx = avcfgi_getconfigindex(config, name);
if (idx < 0){
char err[128];
printf("1\n");
snprintf(err, 127, "AVCFG: Unknown variable %s\n", name);
avcfgi_fatal(err, config->delegate);
}
return (long long)config->values[idx];
}
long long avcfg_geti_op(avcfg_config* config, const char* name, long long opt){
int idx = avcfgi_getconfigindex(config, name);
if (idx < 0){
return opt;
}
return (long long)config->values[idx];
}
int avcfgi_getconfigindex(avcfg_config* config, const char* name){
for(int i = 0; config->keys[i]; i++){
if (strcmp(config->keys[i], name) == 0)
return i;
}
return -1;
}
/* default delegate preperation */
void avcfgi_default_onerror (const char* message, avcfg_delegate delegate);
char* avcfgi_default_loadfile (const char* filename, avcfg_delegate delegate);
int avcfgi_default_lookupsymbol (const char* name, double* value, avcfg_delegate delegate);
int avcfgi_default_evaluatefunction(const char* name, double param, double* value, avcfg_delegate delegate);
void avcfgi_installdefaultdelegate(avcfg_delegate* delegate){
if (!delegate->onerror) delegate->onerror = avcfgi_default_onerror;
if (!delegate->loadfile) delegate->loadfile = avcfgi_default_loadfile;
if (!delegate->lookupsymbol) delegate->lookupsymbol = avcfgi_default_lookupsymbol;
if (!delegate->evaluatefunction)delegate->evaluatefunction = avcfgi_default_evaluatefunction;
}
void avcfgi_default_onerror(const char* message, avcfg_delegate delegate){
fprintf(stderr, message);
#if defined(_DEBUG) && defined(_WIN32)
__debugbreak();
#endif
exit(1);
}
char* avcfgi_default_loadfile(const char* filename, avcfg_delegate delegate){
FILE *f = fopen(filename, "rb");
if (!f) {
char buffer[1024];
snprintf(buffer,1023, "AVCFG: Could not read file %s!\n", filename);
avcfgi_fatal(buffer, delegate);
}
fseek(f, 0, SEEK_END);
int len = ftell(f);
char* out = (char*)malloc(len+1);
out[len] = 0;
fseek(f, 0, SEEK_SET);
fread(out, 1, len, f);
fclose(f);
return out;
}
int avcfgi_default_lookupsymbol(const char* name, double* value, avcfg_delegate delegate){
#define doconst(varname, varvalue) if (strcmp(name, #varname) == 0){*value=varvalue; return 1;}
doconst(pi, 3.141592653589793);
doconst(e, 2.718281828459045);
doconst(true, 1.0);
doconst(false, 0.0);
#undef doconst
return 0;
}
int avcfgi_default_evaluatefunction(const char* name, double param, double* value, avcfg_delegate delegate){
#define dofunc(func) if (strcmp(name, #func ) == 0) {*value = func(param); return 1;}
dofunc(cos);
dofunc(sin);
dofunc(tan);
dofunc(acos);
dofunc(asin);
dofunc(atan);
dofunc(cosh);
dofunc(sinh);
dofunc(tanh);
dofunc(exp);
dofunc(log);
dofunc(sqrt);
#undef dofunc
return 0;
}
/* loading and parsing */
void avcfgi_skipspaces (char** data);
void avcfgi_skiptonewline(char** data);
char* avcfgi_readident (char** data);
void avcfgi_loadfile_rec(const char* path, avcfg_delegate delegate, avcfgi_hashtable hash){
char* data = delegate.loadfile(path, delegate);
char* begin = data;
int line = 1;
avcfgi_skipspaces(&data);
while(*data){
if (*data == '['){
/* section: we ignore this */
while(*data && *data != ']'){
if(*data == '\n') line++;
data++;
}
if (*data != ']'){
char err[128];
printf("1\n");
snprintf(err, 127, "AVCFG: Error missing ']' in line %i, file %s\n", line, path);
avcfgi_fatal(err, delegate);
}
data++;
}else if (*data == '{'){
/* include */
data++;
char* first = data;
while(*data && *data != '}'){
if(*data == '\n') line++;
data++;
}
if (*data != '}'){
char err[128];
printf("2\n");
snprintf(err, 127, "AVCFG: Error missing '}' in line %i, file %s\n", line, path);
avcfgi_fatal(err, delegate);
}
char *newfile = (char*)malloc(data-first+1);
memcpy(newfile, first, data-first);
newfile[data-first] = 0;
data++;
avcfgi_loadfile_rec(newfile, delegate, hash);
} else if (*data == '#'){
/* comment line */
while(*data && *data != '\n'){
data++;
}
if(*data){
data++;
line++;
}
} else if (isalpha(*data)){
/* symbol def */
char* ident = avcfgi_readident(&data);
avcfgi_skipspaces(&data);
if (*data != '='){
char err[128];
printf("3\n");
snprintf(err, 127, "AVCFG: Error missing '=' in line %i, file %s\n", line, path);
avcfgi_fatal(err, delegate);
}
data++;
char* first = data;
while(*data && *data != '\n' && *data != '#'){
data++;
}
char *value = (char*)malloc(data-first+1);
memcpy(value, first, data-first);
value[data-first] = 0;
avcfgi_symbol* target = avcfgi_symbolbyname(ident, hash);
if(!target){
/* Element not found, add at beginning of list */
target = (avcfgi_symbol*)malloc(sizeof(avcfgi_symbol));
target->key = ident;
target->status = AVCFGI_UNUSED;
target->filename = NULL;
avcfgi_addsymbol(target, hash);
}else{
free(target->value);
free(target->filename);
}
target->value = value;
target->linenumber = line;;
target->filename = avcfgi_strdup(path);
} else if (*data == '\n'){
/* skip newline */
line++;
data++;
}else if (*data == '\r'){
/* ignore windows line endings */
data++;
}else{
/* error */
char err[128];
snprintf(err, 127, "AVCFG: Unknown symbal %i in line %i file %s\n", (int)*data, line, path);
avcfgi_fatal(err, delegate);
}
avcfgi_skipspaces(&data);
}
free(begin);
}
/* dependecy sort and elevation */
double avcfgi_evaluateExpr (avcfgi_symbol *symbol, char** data, avcfg_delegate delegate, avcfgi_hashtable hash);
double avcfgi_evaluateTerm (avcfgi_symbol *symbol, char** data, avcfg_delegate delegate, avcfgi_hashtable hash);
double avcfgi_evaluatePow (avcfgi_symbol *symbol, char** data, avcfg_delegate delegate, avcfgi_hashtable hash);
double avcfgi_evaluateBra (avcfgi_symbol *symbol, char** data, avcfg_delegate delegate, avcfgi_hashtable hash);
double avcfgi_evaluateScalar(avcfgi_symbol *symbol, char** data, avcfg_delegate delegate, avcfgi_hashtable hash);
double avcfgi_read_number (char** data);
void avcfgi_resolvesymbol(avcfgi_symbol *symbol, avcfg_delegate delegate, avcfgi_hashtable hash){
if (symbol->status == AVCFGI_DONE){
return;
}
if (symbol->status == AVCFGI_INFLIGHT){
char buffer[256];
snprintf(buffer, 255, "AVCFG: Cyclic reference on %s in line %i, file %s\n", symbol->key, symbol->linenumber, symbol->filename);
avcfgi_fatal(buffer, delegate);
return;
}
symbol->status = AVCFGI_INFLIGHT;
char *begin = symbol->value;
symbol->finalvalue = avcfgi_evaluateExpr(symbol, &begin, delegate, hash);
avcfgi_skipspaces(&begin);
if (*begin){
char buffer[256];
snprintf(buffer, 255, "AVCFG: Unexpected symbol %x in line %i, file %s\n", *begin, symbol->linenumber, symbol->filename);
avcfgi_fatal(buffer, delegate);
}
symbol->status = AVCFGI_DONE;
}
double avcfgi_evaluateExpr(avcfgi_symbol *symbol, char** data, avcfg_delegate delegate, avcfgi_hashtable hash){
avcfgi_skipspaces(data);
double val = avcfgi_evaluateTerm(symbol, data, delegate, hash);
while(**data == '+' || **data == '-'){
if (**data == '+'){
(*data)++;
val+= avcfgi_evaluateTerm(symbol, data, delegate, hash);
}else{
(*data)++;
val-= avcfgi_evaluateTerm(symbol, data, delegate, hash);
}
avcfgi_skipspaces(data);
}
return val;
}
double avcfgi_evaluateTerm(avcfgi_symbol *symbol, char** data, avcfg_delegate delegate, avcfgi_hashtable hash){
avcfgi_skipspaces(data);
double val = avcfgi_evaluatePow(symbol, data, delegate, hash);
while(**data == '*' || **data == '/'){
if (**data == '*'){
(*data)++;
val*= avcfgi_evaluatePow(symbol, data, delegate, hash);
}else{
(*data)++;
val/= avcfgi_evaluatePow(symbol, data, delegate, hash);
}
avcfgi_skipspaces(data);
}
return val;
}
double avcfgi_evaluatePow(avcfgi_symbol *symbol, char** data, avcfg_delegate delegate, avcfgi_hashtable hash){
avcfgi_skipspaces(data);
double val = avcfgi_evaluateBra(symbol, data, delegate, hash);
while(**data == '^'){
(*data)++;
val = pow(val, avcfgi_evaluateBra(symbol, data, delegate, hash));
avcfgi_skipspaces(data);
}
return val;
}
double avcfgi_evaluateBra(avcfgi_symbol *symbol, char** data, avcfg_delegate delegate, avcfgi_hashtable hash){
avcfgi_skipspaces(data);
if(**data == '('){
(*data)++;
double val = avcfgi_evaluateExpr(symbol, data, delegate, hash);
avcfgi_skipspaces(data);
if (**data != ')'){
char error[256];
snprintf(error, 255, "AVCFG: Missing closing braket in line %i file %s", symbol->linenumber, symbol->filename);
avcfgi_fatal(error, delegate);
}
(*data)++;
return val;
}else{
return avcfgi_evaluateScalar(symbol, data, delegate, hash);
}
}
double avcfgi_evaluateScalar(avcfgi_symbol *symbol, char** data, avcfg_delegate delegate, avcfgi_hashtable hash){
avcfgi_skipspaces(data);
char fst = **data;
if (fst == '-'){
/* unary minus */
(*data)++;
return -avcfgi_evaluateTerm(symbol, data, delegate, hash);
}else if(isdigit(fst) || fst == '.'){
/* number literal */
return avcfgi_read_number(data);
}else if (isalpha(fst)){
/* var or function */
char* ident = avcfgi_readident(data);
avcfgi_skipspaces(data);
if (**data == '('){
/* bracket */
double param = avcfgi_evaluateBra(symbol, data, delegate, hash);
double res;
if (!delegate.evaluatefunction(ident, param, &res, delegate)){
char err[256];
snprintf(err, 255, "AVCFG: Unknown function %s called in line %i in file %s\n", ident, symbol->linenumber, symbol->filename);
avcfgi_fatal(err, delegate);
}
return res;
}else{
/* variable */
double ret;
avcfgi_symbol* var = avcfgi_symbolbyname(ident, hash);
if (var){
avcfgi_resolvesymbol(var, delegate, hash);
return var->finalvalue;
}else if(delegate.lookupsymbol(ident, &ret, delegate)){
return ret;
}else{
char err[256];
snprintf(err, 255, "AVCFG: Unknown variable %s in line %i in file %s\n", ident, symbol->linenumber, symbol->filename);
avcfgi_fatal(err, delegate);
return 0;
}
}
}else{
/* error */
char err[256];
snprintf(err, 255, "AVCFG: Unexpected symbol %c in line %i in file %s\n", fst, symbol->linenumber, symbol->filename);
avcfgi_fatal(err, delegate);
return 0;
}
}
/* Parsing primitives */
void avcfgi_skipspaces(char** data){
while(**data == ' ' || **data == '\t') (*data)++;
}
void avcfgi_skiptonewline(char** data){
while(**data && **data != '\n') (*data)++;
}
char* avcfgi_readident(char** data){
char* begin = *data;
while(isalnum(**data) || **data == '_') (*data)++;
char *end = *data;
int len = end-begin;
char* out = (char*)malloc(len+1);
memcpy(out, begin, len);
out[len] = 0;
return out;
}
/* Utils */
long avcfgi_readsignedint(char** begin);
double avcfgi_read_number(char** begin){
double i = avcfgi_readsignedint(begin);
if (**begin == '.'){
(*begin)++;
double fac = 0.1;
while (isdigit(**begin)) {
i += (**begin - '0')*fac;
fac*=0.1;
(*begin)++;
}
}
if (**begin != 'e'){
return i;
}
(*begin)++;
double exp = avcfgi_readsignedint(begin);
return i*pow(10, exp);
}
long avcfgi_readsignedint(char** begin){
long sign = 1;
if (**begin == '-'){
sign = -1;
(*begin)++;
}
long i = 0;
while (isdigit(**begin)) {
i *= 10;
i += **begin - '0';
(*begin)++;
}
return i*sign;
}
/* Utils */
int avcfgi_hashidx(const char *str);
avcfgi_symbol* avcfgi_symbolbyname(const char* name, avcfgi_hashtable hash){
int idx = avcfgi_hashidx(name);
avcfgi_symbol* list = hash[idx];
while(list){
if(strcmp(list->key, name) == 0){
return list;
}
list = list->next;
}
return NULL;
}
void avcfgi_addsymbol(avcfgi_symbol* symbol, avcfgi_hashtable hash){
int idx = avcfgi_hashidx(symbol->key);
symbol->next = hash[idx];
hash[idx] = symbol;
}
int avcfgi_hashidx(const char *str){
/* djb2 hash */
unsigned hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return (int)(hash%AVCFG_HASHSIZE);
}
char* avcfgi_strdup(const char* c){
int n = (int)strlen(c);
char* out = (char*)malloc(n+1);
out[n] = 0;
memcpy(out, c, n);
return out;
}
#ifdef _MSC_VER
__declspec(noreturn) void avcfgi_fatal(const char* msg, avcfg_delegate delegate){
#else
void avcfgi_fatal(const char* msg, avcfg_delegate delegate){
#endif
delegate.onerror(msg, delegate);
/* this should never happen! */
exit(1);
}
#endif
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment