diff --git a/emu/cfg.c b/emu/cfg.c new file mode 100644 index 00000000000..26f5bfcf4df --- /dev/null +++ b/emu/cfg.c @@ -0,0 +1,191 @@ +#include "cfg.h" + +#include +#include +#include +#include + + +static int cfg_buffer(cfg_t *cfg, char c) { + // Amortize double + if (cfg->blen == cfg->bsize) { + size_t nsize = cfg->bsize * 2; + char *nbuf = malloc(nsize); + if (!nbuf) { + return -ENOMEM; + } + + memcpy(nbuf, cfg->buf, cfg->bsize); + free(cfg->buf); + cfg->buf = nbuf; + cfg->bsize = nsize; + } + + cfg->buf[cfg->blen] = c; + cfg->blen += 1; + return 0; +} + +static int cfg_attr(cfg_t *cfg, unsigned key, unsigned val) { + // Amortize double + if (cfg->len == cfg->size) { + size_t nsize = cfg->size * 2; + struct cfg_attr *nattrs = malloc(nsize*sizeof(struct cfg_attr)); + if (!nattrs) { + return -ENOMEM; + } + + memcpy(nattrs, cfg->attrs, cfg->size*sizeof(struct cfg_attr)); + free(cfg->attrs); + cfg->attrs = nattrs; + cfg->size = nsize; + } + + // Keep attrs sorted for binary search + unsigned i = 0; + while (i < cfg->len && + strcmp(&cfg->buf[key], + &cfg->buf[cfg->attrs[i].key]) > 0) { + i += 1; + } + + memmove(&cfg->attrs[i+1], &cfg->attrs[i], + (cfg->size - i)*sizeof(struct cfg_attr)); + cfg->attrs[i].key = key; + cfg->attrs[i].val = val; + cfg->len += 1; + return 0; +} + +static bool cfg_match(FILE *f, const char *matches) { + char c = getc(f); + ungetc(c, f); + + for (int i = 0; matches[i]; i++) { + if (c == matches[i]) { + return true; + } + } + + return false; +} + +int cfg_create(cfg_t *cfg, const char *filename) { + // start with some initial space + cfg->len = 0; + cfg->size = 4; + cfg->attrs = malloc(cfg->size*sizeof(struct cfg_attr)); + + cfg->blen = 0; + cfg->bsize = 16; + cfg->buf = malloc(cfg->size); + + FILE *f = fopen(filename, "r"); + if (!f) { + return -errno; + } + + while (!feof(f)) { + int err; + + while (cfg_match(f, " \t\v\f")) { + fgetc(f); + } + + if (!cfg_match(f, "#\r\n")) { + unsigned key = cfg->blen; + while (!cfg_match(f, " \t\v\f:#") && !feof(f)) { + if ((err = cfg_buffer(cfg, fgetc(f)))) { + return err; + } + } + if ((err = cfg_buffer(cfg, 0))) { + return err; + } + + while (cfg_match(f, " \t\v\f")) { + fgetc(f); + } + + if (cfg_match(f, ":")) { + fgetc(f); + while (cfg_match(f, " \t\v\f")) { + fgetc(f); + } + + unsigned val = cfg->blen; + while (!cfg_match(f, " \t\v\f#\r\n") && !feof(f)) { + if ((err = cfg_buffer(cfg, fgetc(f)))) { + return err; + } + } + if ((err = cfg_buffer(cfg, 0))) { + return err; + } + + if ((err = cfg_attr(cfg, key, val))) { + return err; + } + } else { + cfg->blen = key; + } + } + + while (!cfg_match(f, "\r\n") && !feof(f)) { + fgetc(f); + } + fgetc(f); + } + + return 0; +} + +void cfg_destroy(cfg_t *cfg) { + free(cfg->attrs); +} + +bool cfg_has(cfg_t *cfg, const char *key) { + return cfg_get(cfg, key, 0); +} + +const char *cfg_get(cfg_t *cfg, const char *key, const char *def) { + // binary search for attribute + int lo = 0; + int hi = cfg->len-1; + + while (lo <= hi) { + int i = (hi + lo) / 2; + int cmp = strcmp(key, &cfg->buf[cfg->attrs[i].key]); + if (cmp == 0) { + return &cfg->buf[cfg->attrs[i].val]; + } else if (cmp < 0) { + hi = i-1; + } else { + lo = i+1; + } + } + + return def; +} + +ssize_t cfg_geti(cfg_t *cfg, const char *key, ssize_t def) { + const char *val = cfg_get(cfg, key, 0); + if (!val) { + return def; + } + + char *end; + ssize_t res = strtoll(val, &end, 0); + return (end == val) ? def : res; +} + +size_t cfg_getu(cfg_t *cfg, const char *key, size_t def) { + const char *val = cfg_get(cfg, key, 0); + if (!val) { + return def; + } + + char *end; + size_t res = strtoull(val, &end, 0); + return (end == val) ? def : res; +} diff --git a/emu/cfg.h b/emu/cfg.h new file mode 100644 index 00000000000..8b4780d5b23 --- /dev/null +++ b/emu/cfg.h @@ -0,0 +1,74 @@ +/* + * Simple config parser + * + * Copyright (c) 2016 Christopher Haster + * Distributed under the MIT license + */ +#ifndef CFG_H +#define CFG_H + +#include +#include +#include + +// This is a simple parser for config files +// +// The cfg file format is dumb simple. Attributes are +// key value pairs separated by a single colon. Delimited +// by comments (#) and newlines (\r\n) and trims +// whitespace ( \t\v\f) +// +// Here's an example file +// # Here is a dump example +// looky: it's_an_attribute +// hey_look: another_attribute +// +// huh: yeah_that's_basically_it # basically it + +// Internal config structure +typedef struct cfg { + size_t len; + size_t size; + + size_t blen; + size_t bsize; + char *buf; + + struct cfg_attr { + unsigned key; + unsigned val; + } *attrs; +} cfg_t; + + + +// Creates a cfg object and reads in the cfg file from the filename +// +// If the cfg_read fails, returns a negative value from the underlying +// stdio functions +int cfg_create(cfg_t *cfg, const char *filename); + +// Destroys the cfg object and frees any used memory +void cfg_destroy(cfg_t *cfg); + +// Checks if a cfg attribute exists +bool cfg_has(cfg_t *cfg, const char *key); + +// Retrieves a cfg attribute as a null-terminated string +// +// If the attribute does not exist, returns the string passed as def +const char *cfg_get(cfg_t *cfg, const char *key, const char *def); + +// Retrieves a cfg attribute parsed as an int +// +// If the attribute does not exist or can't be parsed, returns the +// integer passed as def +ssize_t cfg_geti(cfg_t *cfg, const char *name, ssize_t def); + +// Retrieves a cfg attribute parsed as an unsigned int +// +// If the attribute does not exist or can't be parsed, returns the +// integer passed as def +size_t cfg_getu(cfg_t *cfg, const char *name, size_t def); + +#endif