summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2025-09-14 01:08:06 -0400
committerpommicket <pommicket@gmail.com>2025-09-14 01:08:06 -0400
commit1ed5d20169194631b3da49982df82774fcba7cb7 (patch)
treebcb6b37ca31bcb26c9e8ad5fbba1b9137bfe85d2
parentd8192f73672488234bd12319229402153e7b6c21 (diff)
error to string, conf copy
-rw-r--r--examples/read_conf.c15
-rw-r--r--pom.c131
-rw-r--r--pom.h9
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);
}
diff --git a/pom.c b/pom.c
index 5a4433f..01d9efb 100644
--- a/pom.c
+++ b/pom.c
@@ -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;
+}
diff --git a/pom.h b/pom.h
index 147720a..ea38a3d 100644
--- a/pom.h
+++ b/pom.h
@@ -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.