diff options
-rw-r--r-- | examples/read_conf.c | 15 | ||||
-rw-r--r-- | pom.c | 131 | ||||
-rw-r--r-- | pom.h | 9 |
3 files changed, 127 insertions, 28 deletions
diff --git a/examples/read_conf.c b/examples/read_conf.c index 6b7c18f..54a1e86 100644 --- a/examples/read_conf.c +++ b/examples/read_conf.c @@ -8,7 +8,12 @@ int main(int argc, char **argv) { pom_error *error; pom_conf *conf = pom_load_path(argc >= 2 ? argv[1] : "conf.pom", &error); if (!conf) { - pom_error_print(error); + char *string = pom_error_to_string(error); + for (char *s=string; *s; s++) + if (*s >= 'a' && *s <= 'z') + *s += 'A' - 'a'; + printf("%s\n",string); + free(string); free(error); return EXIT_FAILURE; } @@ -21,8 +26,12 @@ int main(int argc, char **argv) { free(error); return EXIT_FAILURE; } - pom_conf_merge(conf,pom_conf_section(conf2,"j")); - pom_conf_print(conf); + pom_conf *copy = pom_conf_copy(conf); + pom_conf_merge(copy,pom_conf_section(conf2,"j")); + pom_conf *copy2 = pom_conf_copy(copy); + pom_conf_print(copy2); pom_conf_free(conf); + pom_conf_free(copy); + pom_conf_free(copy2); pom_conf_free(conf2); } @@ -1,7 +1,5 @@ /* TODO: -- error_to_string -- conf_copy - typed get functions */ #include "pom.h" @@ -76,7 +74,7 @@ struct pom_conf { struct conf_section { const char *key; - struct pom_conf conf; + pom_conf conf; }; // holds the "root" of a configuration @@ -85,7 +83,7 @@ struct main_conf { struct to_free *to_free_head, *to_free_tail; // can return this from pom_conf_section when the section is mpety // (so we don't have to store empty sections) - struct pom_conf empty_section; + pom_conf empty_section; }; struct pom_item_iter { @@ -284,6 +282,28 @@ parser_out_of_memory(struct parser *parser) { parser->out_of_memory = true; } +char * +pom_error_to_string(pom_error *error) { + // first, calculate # of bytes we have to allocate for the string + size_t bytes_required = 16; + for (const pom_error *e = error; e; e = e->next) + bytes_required += strlen(e->file) + strlen(e->message) + 32; + char *string = malloc(bytes_required); + if (!string) return NULL; + if (!error) { + strcpy(string, "No error."); + return string; + } + char *s = string; + strcpy(s, "Error:\n"); + s = strchr(s, 0); + for (const pom_error *e = error; e; e = e->next) { + sprintf(s, "%s:%" PRIu64 ": %s\n", e->file, e->line, e->message); + s = strchr(s, 0); + } + return string; +} + static POM__MUST_USE_L bool parser_realloc_(struct parser *parser, void *ptr, size_t elem_size, size_t *pcapacity, size_t new_capacity) POM__MUST_USE_R; static bool parser_realloc_(struct parser *parser, void *ptr, size_t elem_size, size_t *pcapacity, size_t new_capacity) { @@ -819,17 +839,12 @@ conf_calloc(struct main_conf *conf, size_t nmemb, size_t sz) { } static void -conf_free_inner(struct main_conf *conf) { +conf_free(struct main_conf *conf) { + if (!conf) return; for (struct to_free *next, *f = conf->to_free_head; f; f = next) { next = f->next; free(f); } -} - -static void -conf_free(struct main_conf *conf) { - if (!conf) return; - conf_free_inner(conf); free(conf); } @@ -1330,6 +1345,12 @@ pom_conf_section(const pom_conf *conf, const char *key) { return &conf->main->empty_section; } +#if HAVE_ATOMICS +#define copy_atomic(dest, src) atomic_init(dest, atomic_load_explicit(src, memory_order_relaxed)) +#else +#define copy_atomic(dest, src) *(dest) = *(src) +#endif + bool pom_conf_merge(pom_conf *conf, const pom_conf *other) { check_conf(conf); @@ -1371,7 +1392,7 @@ pom_conf_merge(pom_conf *conf, const pom_conf *other) { new_item->value = item_conf->value; new_item->line = item_conf->line; new_item->file = item_conf->file; - atomic_init(&new_item->read, atomic_load_explicit(&item_conf->read, memory_order_relaxed)); + copy_atomic(&new_item->read, &item_conf->read); } else { if (cmp == 0) i_conf++; // skip item with same key in `conf`. @@ -1380,25 +1401,25 @@ pom_conf_merge(pom_conf *conf, const pom_conf *other) { // copy all the fields, since they might have to outlive `other`. new_item->key = string; strcpy(string, item_other->key + other->prefix_len); - string += strlen(string) + 1; + string = strchr(string, 0) + 1; new_item->value = string; strcpy(string, item_other->value); - string += strlen(string) + 1; + string = strchr(string, 0) + 1; new_item->line = item_other->line; if (item_other->file == prev_file_other) { - new_item->file = prev_file_new; - } else { // if this item's file is the same as the last one from `other`, // just use the same pointer. - // this isn't perfect since keys from different files can be interleaved, + // this isn't perfect de-duplication, + /// since keys from different files can be interleaved, // but it's good enough. - new_item->file = string; - prev_file_new = string; + new_item->file = prev_file_new; + } else { + new_item->file = prev_file_new = string; prev_file_other = item_other->file; strcpy(string, item_other->file); - string += strlen(string) + 1; + string = strchr(string, 0) + 1; } - atomic_init(&new_item->read, atomic_load_explicit(&item_other->read, memory_order_relaxed)); + copy_atomic(&new_item->read, &item_other->read); } } assert(string <= new_string_data + new_string_data_len); @@ -1439,3 +1460,71 @@ pom_conf_print(const pom_conf *conf) { printf("\"\n"); } } + +pom_conf * +pom_conf_copy(const pom_conf *conf) { + check_conf(conf); + struct main_conf *new_main = calloc(1, sizeof *new_main); + if (!new_main) return NULL; + pom_conf *new_root = conf_calloc(new_main, 1, sizeof *new_root); + if (!new_root) { + conf_free(new_main); + return NULL; + } + new_root->main = new_main; + const size_t prefix_len = conf->prefix_len; + // calculate # of bytes we have to allocate for copies of string data + size_t string_bytes_required = 0; + for (size_t i = 0; i < conf->items_count; i++) { + const struct conf_item *item = &conf->items[i]; + if (i == 0 || item->file != item[-1].file) { + string_bytes_required += strlen(item->file) + 1; + } + string_bytes_required += strlen(item->key) + 1 - prefix_len + + strlen(item->value) + 1; + } + // allocate new items array + string data all at once + struct conf_item *new_items = conf_calloc(new_main, + string_bytes_required + conf->items_count * sizeof *conf->items, + 1); + if (!new_items) { + conf_free(new_main); + return NULL; + } + new_root->items = new_items; + new_root->items_count = conf->items_count; + char *new_string_data = (char *)(new_items + conf->items_count); + char *string = new_string_data; + const char *prev_filename = NULL, *prev_filename_copy = NULL; + for (size_t i = 0; i < conf->items_count; i++) { + const struct conf_item *item = &conf->items[i]; + struct conf_item *new_item = &new_items[i]; + if (item->file == prev_filename) { + // if this item's file is the same as the last one, + // just use the same pointer. + // this isn't perfect de-duplication, + // since keys from different files can be interleaved, + // but it's good enough. + new_item->file = prev_filename_copy; + } else { + new_item->file = prev_filename_copy = string; + prev_filename = item->file; + strcpy(string, item->file); + string = strchr(string, 0) + 1; + } + new_item->line = item->line; + new_item->key = string; + strcpy(string, item->key + conf->prefix_len); + string = strchr(string, 0) + 1; + new_item->value = string; + strcpy(string, item->value); + string = strchr(string, 0) + 1; + copy_atomic(&new_item->read, &item->read); + } + assert(string <= new_string_data + string_bytes_required); + if (!conf_create_sections(new_root)) { + conf_free(new_main); + return NULL; + } + return new_root; +} @@ -10,8 +10,9 @@ /// provided that C11 atomics are available /// (`__STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)`). /// But beware of race conditions when using \ref pom_conf_next_unread_key -/// (ensure there is synchronization so that all threads -/// have certainly read their keys before calling it). +/// — this will not lead to UB but you may get unexpected results — +/// ensure there is synchronization so that all threads +/// have certainly read their keys before calling it. /// /// If C11 atomics are not available, you can almost certainly still get away /// with sharing configurations across threads, as long as you use proper @@ -203,7 +204,7 @@ pom_error_print(const pom_error *error); /// Includes every error in an error list (see \ref pom_error_next). POM__MUST_USE_L char * -pom_error_to_string(const pom_error *error) +pom_error_to_string(pom_error *error) POM__MUST_USE_R; /// Returns `true` if `key` is present in `conf`, `false` otherwise. @@ -409,7 +410,7 @@ pom_conf_next_item(const pom_conf *conf, pom_item_iter **iter); /// /// The copy must be freed with \ref pom_conf_free. /// -/// Returns `NULL` (but does not set `conf`'s error) in the (rare) case of out-of-memory. +/// Returns `NULL` on out-of-memory. /// /// The copy is entirely independent from `conf` — it can be passed to a separate /// thread without worry. |