commit 435fab86e3cec53ff2ee5aef7615859f9fbf98bf Author: hellerve Date: Wed Mar 18 23:08:40 2020 +0100 initial diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2cd742 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# cfg + +is a spartan fast configuration language. + +It has numbers, strings, lists, and sections. + +The reference implementation is WIP. I just got to finishing the data type and +pretty printer today, a parser will be provided soon. I didn’t quite create a +correct implementation today. There will most certainly also be memory leaks. +I promised a friend I would share this “later today”, though, and I’m not one +to break promises just because my code is crap. You have been warned. + +You’re not alone: I also wish the code were documented. + +## Example + +`cfg` has no comments, but let’s pretend we had `#` comments. + +``` +# lists start with a name, then an indent, and then a value +my_list + - "value" + - 12 + +# strings are quoted +my_string "this is a string" + +# all numbers are doubles +my_num 42.0 + +# sections have names, and are indented by 2 +my_section + my_inner_string "inner" + my_second_numer 23.0 +``` + +Keys cannot contain spaces. This is all. + +It is a simple format, some might think it is too simple. It is, however, +possible, to write a simple, fast implementation in a few hundred lines of C +(QED), and that might be worth a bit of reduction. + +See the [`examples/](examples/) directory for an example of how to use the +pretty printer. + +
+ +Have fun! diff --git a/cfg.h b/cfg.h new file mode 100644 index 0000000..78c3b47 --- /dev/null +++ b/cfg.h @@ -0,0 +1,446 @@ +#include +#include +#include +#include +#include +#include + +// Utilities + +size_t max(size_t a, size_t b) { return a > b ? a : b; } + +// Data types: list, string and map + +//// Constants + +#define DEFAULT_LIST_SIZE 8 +#define GROWTH_FACTOR 2 + +//// Data types + +///// string is a string with a length and a capacity + +typedef struct string { + size_t len; + size_t cap; + char* str; +} string; + +//// Functions + +///// string functions + +string new_string() { + string s; + s.len = 0; + s.cap = 0; + s.str = NULL; + return s; +} + +string new_string_sized(size_t size) { + string s; + s.len = 0; + s.cap = size; + s.str = calloc(size, sizeof(char)); + return s; +} + +void free_string(string s) { + free(s.str); +} + +char* string_cstr(string* s) { + char* c = malloc(s->len+1); + memcpy(c, s->str, s->len); + c[s->len] = '\0'; + return c; +} + +string string_from_cstr(char* c) { + int l = strlen(c); + string s = new_string_sized(l); + s.len = l; + memcpy(s.str, c, s.len); + return s; +} + +void string_grow_min(string* s, size_t min_growth) { + s->cap = max(min_growth, s->cap*GROWTH_FACTOR); + s->str = realloc(s->str, s->cap); +} + +void string_cappend(string* s, char* c) { + size_t l = strlen(c); + if (s->cap < s->len + l) string_grow_min(s, l); + memcpy(s->str+s->len, c, l); + s->len += l; +} + +void string_append(string* a, string* b) { + if (a->cap < a->len + b->len) string_grow_min(a, b->len); + memcpy(a->str+a->len, b->str, b->len); + a->len += b->len; +} + +string string_slice(string* s, size_t front, size_t back) { + assert(front < back); + assert(front < s->len); + assert(back < s->len); + int len = back - front; + string res = new_string_sized(len); + res.len = len; + memcpy(res.str, s->str+front, len); + return res; +} + +bool string_compare(string* a, string* b) { + if (a->len != b->len) return false; + return strncmp(a->str, b->str, a->len) == 0; +} + +// FNV-1a +size_t string_hash(string* s) { + size_t hash = 2166136261; + + for (int i = 0; i < s->len; i++) { + hash ^= s->str[i]; + hash *= 16777619; + } + + return hash; +} + +///// list functions + +///// list is a list with a length and on-demand growing/shrinking + +#define LIST(type) \ +typedef struct list_##type { \ + size_t len; \ + size_t cap; \ + type* data; \ +} list_##type; \ + \ +list_##type new_list_sized_##type(size_t size) { \ + list_##type l; \ + l.len = 0; \ + l.cap = size; \ + l.data = calloc(size, sizeof(type)); \ + return l; \ +} \ + \ +list_##type new_list_##type() { \ + return new_list_sized_##type(DEFAULT_LIST_SIZE); \ +} \ + \ +void free_list_##type(list_##type l) { \ + free(l.data); \ +} \ + \ +void list_grow_##type(list_##type* l) { \ + if (l->cap == 0) l->cap = DEFAULT_LIST_SIZE; \ + else l->cap *= GROWTH_FACTOR; \ + l->data = realloc(l->data, l->cap*sizeof(type)); \ +} \ + \ +void list_shrink_##type(list_##type* l) { \ + l->cap /= GROWTH_FACTOR; \ + l->data = realloc(l->data, l->cap*sizeof(type)); \ +} \ + \ +void list_push_##type(list_##type* l, type elem) { \ + if (l->len == l->cap) list_grow_##type(l); \ + l->data[l->len] = elem; \ + l->len++; \ +} \ + \ +type list_pop_##type(list_##type* l) { \ + if (l->len*GROWTH_FACTOR < l->cap) list_shrink_##type(l); \ + return l->data[--l->len]; \ +} \ + \ +type list_nth_##type(list_##type* l, size_t n) { \ + assert(n >= 0); \ + assert(n < l->len); \ + return l->data[n]; \ +} \ + \ +void list_set_nth_##type(list_##type* l, size_t n, type elem) { \ + assert(n >= 0); \ + assert(n < l->len); \ + l->data[n] = elem; \ +} \ + +///// map is a hashmap + +#define MAP(key_type, val_type, hash, cmp) \ +typedef struct entry_##key_type##_##val_type { \ + key_type key; \ + val_type val; \ +} entry_##key_type##_##val_type; \ + \ +LIST(entry_##key_type##_##val_type); \ +LIST(list_entry_##key_type##_##val_type); \ +LIST(key_type); \ +LIST(val_type); \ + \ +typedef struct map_##key_type##_##val_type { \ + list_list_entry_##key_type##_##val_type entries; \ + size_t size; \ +} map_##key_type##_##val_type; \ + \ +map_##key_type##_##val_type new_map_##key_type##_##val_type(size_t size) { \ + int i; \ + map_##key_type##_##val_type m;\ + m.entries = \ + new_list_sized_list_entry_##key_type##_##val_type(size); \ + for (i = 0; i < size; i++) list_push_list_entry_##key_type##_##val_type(&m.entries, new_list_entry_##key_type##_##val_type()); \ + m.size = size; \ + return m; \ +} \ + \ +void free_map_##key_type##_##val_type(map_##key_type##_##val_type m) { \ + free_list_list_entry_##key_type##_##val_type(m.entries); \ +} \ + \ +void map_put_##key_type##_##val_type(map_##key_type##_##val_type* m, key_type key, val_type val) { \ + size_t hashed = hash(&key) % m->size; \ + entry_##key_type##_##val_type e; \ + e.key = key; \ + e.val = val; \ + list_entry_##key_type##_##val_type bucket = \ + list_nth_list_entry_##key_type##_##val_type(&m->entries, hashed); \ + list_push_entry_##key_type##_##val_type(&bucket, e); \ + list_set_nth_list_entry_##key_type##_##val_type(&m->entries, hashed, bucket);\ +} \ + \ +val_type map_get_##key_type##_##val_type(map_##key_type##_##val_type* m, key_type key, val_type dflt) { \ + int i; \ + size_t hashed = hash(&key) % m->size; \ + list_entry_##key_type##_##val_type bucket = \ + list_nth_list_entry_##key_type##_##val_type(&m->entries, hashed); \ + \ + for (i = 0; i < bucket.len; i++) { \ + entry_##key_type##_##val_type e = list_nth_entry_##key_type##_##val_type(&bucket, i); \ + if (cmp(&key, &e.key)) return e.val; \ + } \ + \ + return dflt; \ +} \ + \ +list_##key_type map_keys_##key_type##_##val_type(map_##key_type##_##val_type* m) { \ + int i, j; \ + list_entry_##key_type##_##val_type bucket; \ + list_##key_type res = new_list_##key_type(); \ + list_list_entry_##key_type##_##val_type entries = m->entries; \ + \ + for (i = 0; i < entries.len; i++) { \ + bucket = list_nth_list_entry_##key_type##_##val_type(&entries, i); \ + for (j = 0; j < bucket.len; j++) { \ + list_push_##key_type(&res, list_nth_entry_##key_type##_##val_type(&bucket, j).key); \ + } \ + } \ + \ + return res; \ +} \ + \ +list_##val_type map_vals_##key_type##_##val_type(map_##key_type##_##val_type* m) { \ + int i, j; \ + list_entry_##key_type##_##val_type bucket; \ + list_##val_type res = new_list_##val_type(); \ + list_list_entry_##key_type##_##val_type entries = m->entries; \ + \ + for (i = 0; i < entries.len; i++) { \ + bucket = list_nth_list_entry_##key_type##_##val_type(&entries, i); \ + for (j = 0; j < bucket.len; j++) { \ + list_push_##val_type(&res, list_nth_entry_##key_type##_##val_type(&bucket, j).val); \ + } \ + } \ + \ + return res; \ +} \ + \ +list_entry_##key_type##_##val_type map_entries_##key_type##_##val_type(map_##key_type##_##val_type* m) { \ + int i, j; \ + list_entry_##key_type##_##val_type bucket; \ + list_entry_##key_type##_##val_type res = new_list_entry_##key_type##_##val_type(); \ + list_list_entry_##key_type##_##val_type entries = m->entries; \ + \ + for (i = 0; i < entries.len; i++) { \ + bucket = list_nth_list_entry_##key_type##_##val_type(&entries, i); \ + for (j = 0; j < bucket.len; j++) { \ + list_push_entry_##key_type##_##val_type( \ + &res, \ + list_nth_entry_##key_type##_##val_type(&bucket, j) \ + ); \ + } \ + } \ + \ + return res; \ +} \ + +// Config: a simple configuration language + +// Types: a config type and a config value type + +typedef struct config config; +struct list_config_value; + +typedef struct config_value { + char tag; + union { + config* section; + string s; + double d; + struct list_config_value* l; + }; +} config_value; + +config_value zero_config_value = {0}; + +MAP(string, config_value, string_hash, string_compare); + +struct config { + map_string_config_value values; +}; + +#define config_value_section 1 +#define config_value_string 2 +#define config_value_number 3 +#define config_value_list 4 + +//// Functions + +////// config value functions + +bool config_value_compare(config_value* a, config_value* b) { + if (a->tag != b->tag) return false; + switch (a->tag) { + case config_value_section: + return false; + //return map_compare(&a->section->values, &b->section->values); + case config_value_string: + return string_compare(&a->s, &b->s); + case config_value_number: + return a->d == b->d; + case config_value_list: + return false; + //return list_compare(&a->l, &b->l, config_value_compare); + } + return false; +} + +string config_str(config* c, unsigned int indent); + +string config_value_str(config_value* c, unsigned int indent) { + int i, j; + char tmp[128]; + string sub; + string res = new_string(); + config_value val; + + switch (c->tag) { + case config_value_section: + string_cappend(&res, "\n"); + sub = config_str(c->section, indent+2); + string_append(&res, &sub); + return res; + case config_value_string: + string_cappend(&res, "\""); + string_append(&res, &c->s); + string_cappend(&res, "\""); + break; + case config_value_number: + snprintf(tmp, 128, "%lf", c->d); + string_cappend(&res, tmp); + break; + case config_value_list: + for (i = 0; i < c->l->len; i++) { + string_cappend(&res, "\n"); + for (j = 0; j < indent; j++) string_cappend(&res, " "); + string_cappend(&res, " - "); + val = list_nth_config_value(c->l, i); + sub = config_value_str(&val, 0); + string_append(&res, &sub); + free_string(sub); + } + } + return res; +} + +config_value config_section(config* section) { + config_value res; + res.tag = config_value_section; + res.section = section; + return res; +} + +config_value config_string(string s) { + config_value res; + res.tag = config_value_string; + res.s = s; + return res; +} + +config_value config_number(double d) { + config_value res; + res.tag = config_value_number; + res.d = d; + return res; +} + +config_value config_list(list_config_value* l) { + config_value res; + res.tag = config_value_list; + res.l = l; + return res; +} + +////// config functions + +config new_config() { + config c; + c.values = new_map_string_config_value(1024); + return c; +} + +string config_str(config* c, unsigned int indent) { + int i, j; + string key; + config_value val; + list_string keys = map_keys_string_config_value(&c->values); + string res = new_string(); + string tmp; + + for (i = 0; i < keys.len; i++) { + key = list_nth_string(&keys, i); + for (j = 0; j < indent; j++) string_cappend(&res, " "); + string_append(&res, &key); + string_cappend(&res, " "); + val = map_get_string_config_value(&c->values, key, zero_config_value); + tmp = config_value_str(&val, indent); + string_append(&res, &tmp); + free_string(tmp); + string_cappend(&res, "\n"); + } + + return res; +} + +void config_add_section(config* c, string label, config* section) { + assert(c != section && "cannot nest section in itself"); + map_put_string_config_value(&c->values, label, config_section(section)); +} + +void config_add_string(config* c, string label, string s) { + map_put_string_config_value(&c->values, label, config_string(s)); +} + +void config_add_number(config* c, string label, double d) { + map_put_string_config_value(&c->values, label, config_number(d)); +} + +void config_add_list(config* c, string label, list_config_value* l) { + map_put_string_config_value(&c->values, label, config_list(l)); +} diff --git a/examples/nested_simple.c b/examples/nested_simple.c new file mode 100644 index 0000000..5d3c3e1 --- /dev/null +++ b/examples/nested_simple.c @@ -0,0 +1,26 @@ +#include "../cfg.h" + +int main() { + config c = new_config(); + string key; + config_add_number(&c, string_from_cstr("mynum"), 42); + config_add_string(&c, string_from_cstr("mystring"), string_from_cstr("cfg is pretty awesome")); + list_config_value l = new_list_config_value(); + for (int i = 0; i < 10; i += 2) { + config_value v = config_number(i); + list_push_config_value(&l, v); + } + config_add_list(&c, string_from_cstr("mylist"), &l); + config sub = new_config(); + config_add_number(&sub, string_from_cstr("mysecondnum"), 23); + config_add_string(&sub, string_from_cstr("mysecondstring"), string_from_cstr("inner")); + config_add_section(&c, string_from_cstr("mysection"), &sub); + + string s = config_str(&c, 0); + char* cstr = string_cstr(&s); + + printf("%s\n", cstr); + free_string(s); + free(cstr); + return 0; +}