This commit is contained in:
2020-03-18 23:08:40 +01:00
commit 435fab86e3
3 changed files with 520 additions and 0 deletions

48
README.md Normal file
View File

@@ -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 didnt 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 Im not one
to break promises just because my code is crap. You have been warned.
Youre not alone: I also wish the code were documented.
## Example
`cfg` has no comments, but lets 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.
<hr/>
Have fun!

446
cfg.h Normal file
View File

@@ -0,0 +1,446 @@
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
// 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));
}

26
examples/nested_simple.c Normal file
View File

@@ -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;
}