summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2025-09-12 12:43:18 -0400
committerpommicket <pommicket@gmail.com>2025-09-12 12:43:41 -0400
commit9865b70c855500ea3cae76fb8c9986bdf6f249ca (patch)
tree244af1e93164d5aeea9c7a141ac4b5231d3882c6
parenta835e738761238b891847c8303592b504018f017 (diff)
Implement pom_conf_next_item
-rw-r--r--examples/read_conf.c6
-rw-r--r--meson.build4
-rw-r--r--pom.c242
-rw-r--r--pom.h13
-rwxr-xr-xpre-commit.sh5
5 files changed, 248 insertions, 22 deletions
diff --git a/examples/read_conf.c b/examples/read_conf.c
index 091255c..ed33e21 100644
--- a/examples/read_conf.c
+++ b/examples/read_conf.c
@@ -11,4 +11,10 @@ int main(int argc, char **argv) {
free(error);
return EXIT_FAILURE;
}
+ const pom_item *item;
+ pom_item_iter *iter = NULL;
+ while ((item = pom_conf_next_item(conf, &iter))) {
+ printf("Key: %s, Value: %s\n", item->key, item->value);
+ }
+ pom_conf_free(conf);
}
diff --git a/meson.build b/meson.build
index 7721c3f..3a09963 100644
--- a/meson.build
+++ b/meson.build
@@ -1,3 +1,5 @@
-project('pom', 'c')
+project('pom', 'c',
+ # Note: Only C99 is strictly needed
+ default_options: ['c_std=c23'])
library('pom', 'pom.c')
static_library('pom', 'pom.c')
diff --git a/pom.c b/pom.c
index f135b6b..9d4b0e4 100644
--- a/pom.c
+++ b/pom.c
@@ -28,22 +28,44 @@ struct pom_error {
struct main_conf;
+struct to_free {
+ struct to_free *next;
+ // fool's max_align_t
+ union {
+ void *ptr;
+ double d;
+ uint64_t u64;
+ } data[];
+};
+
struct pom_conf {
struct main_conf *main;
+ // this is checked vs main->version_number
+ // to detect when a section is used after a merge
+ uint64_t version_number;
size_t prefix_len;
const struct conf_item *items;
size_t items_count;
};
-struct conf_items {
- const char *key;
- const char *value;
- const pom_conf *section;
+struct pom_item_iter {
+ const pom_conf *conf;
+ const struct conf_item *conf_item;
+ pom_item item;
+};
+
+struct conf_item {
+ const char *key, *value, *file;
+ uint64_t line;
+ const struct pom_conf *section;
};
struct main_conf {
struct conf_item *items;
+ struct to_free *to_free_head, *to_free_tail;
size_t items_count;
+ // increases when merged
+ uint64_t version_number;
};
// temporary error that is eventually converted to a pom_error
@@ -72,8 +94,15 @@ enum utf8_state {
UTF8_STATE_3CONT_LT_90 = 7,
};
+// temporary item that is eventually turned into a conf_item
+struct parser_item {
+ size_t key;
+ size_t value;
+ uint64_t line;
+};
+
struct parser {
- char *filename;
+ const char *filename;
uint64_t line_number;
size_t (*read_func)(void *, char *, size_t);
void *userdata;
@@ -92,8 +121,18 @@ struct parser {
} error_messages;
struct {
char *array;
+ size_t count, capacity;
+ } string_data;
+ struct {
+ char *array;
size_t capacity;
+ size_t len;
} current_section;
+ struct {
+ struct parser_item *array;
+ size_t capacity;
+ size_t count;
+ } items;
bool short_read, eof, out_of_memory, leftover_cr;
// see enum utf8_state -- starting state for future calls to read_func
uint8_t utf8_state;
@@ -243,12 +282,20 @@ parser_append_(struct parser *parser, void *ptr, size_t elem_size, size_t *pcoun
}
}
+#if __STDC_VERSION__ >= 202311
+#define SAFETY_CAST_TYPEOF(t) (typeof(t))
+#elif __GNUC__ >= 4
+#define SAFETY_CAST_TYPEOF(t) (__typeof__(t))
+#else
+#define SAFETY_CAST_TYPEOF(t)
+#endif
+
#define parser_realloc(parser, field, new_capacity) \
parser_realloc_(parser, &parser->field.array, sizeof parser->field.array[0], &parser->field.capacity, new_capacity)
// Adds room for `need` elements to the array `parser.field`,
// and returns a pointer to the first one.
#define parser_append(parser, field, need) \
- parser_append_(parser, &parser->field.array, sizeof parser->field.array[0], &parser->field.count, &parser->field.capacity, need)
+ SAFETY_CAST_TYPEOF(parser->field.array) parser_append_(parser, &parser->field.array, sizeof parser->field.array[0], &parser->field.count, &parser->field.capacity, need)
#define parser_append_one(parser, field) \
parser_append(parser, field, 1)
@@ -483,6 +530,12 @@ check_valid_key(struct parser *parser, const char *key) {
}
static void
+parse_quoted_value(struct parser *parser, const char *first_line) {
+ // TODO
+ abort();
+}
+
+static void
parse_line(struct parser *parser) {
parser_read_line(parser);
char *line = parser->line.array;
@@ -506,11 +559,59 @@ parse_line(struct parser *parser) {
char *current_section = parser->current_section.array;
memcpy(current_section, line, len);
current_section[len] = 0;
+ parser->current_section.len = len;
if (len)
check_valid_key(parser, current_section);
return;
}
printf("%s|%s\n",parser->current_section.array,line);
+ size_t equals_idx;
+ for (size_t i = 0; ; i++) {
+ if (line[i] == '=') {
+ equals_idx = i;
+ break;
+ }
+ if (line[i] == 0) {
+ parser_error(parser, "Line should start with [ or contain an =");
+ return;
+ }
+ }
+ if (equals_idx == 0) {
+ parser_error(parser, "Expected key name before =");
+ return;
+ }
+ size_t key_idx = parser->string_data.count;
+ {
+ // Parse key
+ char *key = parser_append(parser, string_data, parser->current_section.len + 1 + equals_idx + 1);
+ char *p = key;
+ if (parser->current_section.len) {
+ memcpy(p, parser->current_section.array, parser->current_section.len);
+ p += parser->current_section.len;
+ *p++ = '.';
+ }
+ memcpy(p, line, equals_idx);
+ p[equals_idx] = 0;
+ strip_trailing_accepted_spaces(p);
+ check_valid_key(parser, key);
+ }
+ size_t value_start_idx = equals_idx + 1;
+ while (line[value_start_idx] == ' ' || line[value_start_idx] == '\t')
+ value_start_idx++;
+ size_t value_idx = parser->string_data.count;
+ if (line[value_start_idx] == '"' || line[value_start_idx] == '`') {
+ parse_quoted_value(parser, &line[value_start_idx]);
+ } else {
+ char *value = &line[value_start_idx];
+ strip_trailing_accepted_spaces(value);
+ size_t value_sz = strlen(value) + 1;
+ memcpy(parser_append(parser, string_data, value_sz),
+ value, value_sz);
+ }
+ struct parser_item *item = parser_append_one(parser, items);
+ item->key = key_idx;
+ item->value = value_idx;
+ item->line = parser->line_number;
}
static void
@@ -522,6 +623,73 @@ set_error(pom_error **error, pom_error *e) {
}
}
+static void conf_free_list_append(struct main_conf *conf, struct to_free *mem) {
+ mem->next = NULL;
+ if (conf->to_free_tail) {
+ conf->to_free_tail->next = mem;
+ conf->to_free_tail = mem;
+ } else {
+ conf->to_free_head = conf->to_free_tail = mem;
+ }
+}
+
+static void *conf_calloc(struct main_conf *conf, size_t nmemb, size_t sz) {
+ if (nmemb > SIZE_MAX / (2*sz)) return NULL;
+ struct to_free *mem = calloc(1, sizeof(struct to_free) + nmemb * sz);
+ if (!mem) return NULL;
+ conf_free_list_append(conf, mem);
+ return &mem->data[0];
+}
+
+static void 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);
+ }
+ free(conf);
+}
+
+pom_conf *
+parser_finish(struct parser *parser) {
+ if (parser->out_of_memory || parser->errors.count) {
+ return NULL;
+ }
+ struct main_conf *conf = calloc(1, sizeof *conf);
+ if (!conf) {
+ parser_out_of_memory(parser);
+ return NULL;
+ }
+ conf->items_count = parser->items.count;
+ conf->items = conf_calloc(conf, conf->items_count, sizeof(struct conf_item));
+ char *filename = conf_calloc(conf, strlen(parser->filename) + 1, 1);
+ if (!conf->items || !filename) {
+ parser_out_of_memory(parser);
+ return NULL;
+ }
+ // we made room for the to_free header in pom_load.
+ struct to_free *string_data = (struct to_free *)parser->string_data.array;
+ // stop this from getting freed
+ parser->string_data.array = NULL;
+ conf_free_list_append(conf, string_data);
+
+ for (size_t i = 0; i < conf->items_count; i++) {
+ const struct parser_item *parser_item = &parser->items.array[i];
+ struct conf_item *conf_item = &conf->items[i];
+ conf_item->file = filename;
+ conf_item->line = parser_item->line;
+ conf_item->key = ((char*)string_data->data) + (parser_item->key - sizeof(struct to_free));
+ conf_item->value = ((char*)string_data->data) + (parser_item->value - sizeof(struct to_free));
+ conf_item->section = NULL; // TODO
+ }
+ pom_conf *root = conf_calloc(conf, 1, sizeof *root);
+ root->items = conf->items;
+ root->items_count = conf->items_count;
+ root->main = conf;
+ root->prefix_len = 0;
+ return root;
+}
+
pom_conf *
pom_load(const char *filename,
size_t (*read_func)(void *userdata, char *buf, size_t len),
@@ -540,30 +708,28 @@ pom_load(const char *filename,
set_error(error, out_of_memory);
return NULL;
}
- struct parser *parser = calloc(1, sizeof *parser + strlen(filename) + 1);
+ struct parser *parser = calloc(1, sizeof *parser);
if (!parser) {
free(current_section);
set_error(error, out_of_memory);
return NULL;
}
- // store copy of filename just after parser in memory
- strcpy((char *)(parser + 1), filename);
- parser->filename = (char *)(parser + 1);
+ parser->filename = filename;
parser->out_of_memory_error = out_of_memory;
parser->read_func = read_func;
parser->userdata = userdata;
parser->current_section.array = current_section;
+ // make room for to_free header
+ parser_append(parser, string_data, sizeof(struct to_free));
// read into parser->buf, and skip initial BOM if present.
parser_read_to_buf(parser, true);
while (!(parser->eof || parser->out_of_memory))
parse_line(parser);
-
- bool success = true;
+ pom_conf *conf = parser_finish(parser);
+
if (parser->out_of_memory) {
- success = false;
set_error(error, out_of_memory);
} else if (parser->errors.count) {
- success = false;
if (error) {
// shouldn't overflow
size_t len = parser->errors.count * sizeof(pom_error) + parser->error_messages.count + strlen(filename) + 1;
@@ -596,8 +762,10 @@ pom_load(const char *filename,
}
free(parser->line.array);
free(parser->current_section.array);
+ free(parser->string_data.array);
+ free(parser->items.array);
free(parser);
- return success ? NULL : NULL;
+ return conf;
}
static size_t
@@ -629,7 +797,7 @@ pom_load_file(const char *filename, FILE *file, pom_error **error) {
if (!filename)
fatal_error("%s called with NULL file name", __func__);
if (!file)
- fatal_error("%s called with NULL file", __func__);
+ fatal_error("%s called with NULL file", __func__);
return pom_load(filename, read_file, file, error);
}
@@ -650,3 +818,45 @@ pom_load_path(const char *path, pom_error **error) {
return conf;
}
#endif
+
+void
+pom_conf_free(pom_conf *conf) {
+ conf_free(conf->main);
+}
+
+static void
+check_conf(const pom_conf *conf, const char *func) {
+ if (!conf)
+ fatal_error("NULL configuration passed to %s", func);
+ if (conf->version_number != conf->main->version_number)
+ fatal_error("Section obtained via pom_conf_section is being used\n"
+ "(in %s) after pom_conf_merge was called.\n"
+ "This is not allowed.", func);
+}
+#define check_conf(conf) check_conf(conf, __func__)
+
+const pom_item *
+pom_conf_next_item(const pom_conf *conf, pom_item_iter **p_iter) {
+ check_conf(conf);
+ if (!p_iter) fatal_error("NULL iter passed to %s", __func__);
+ if (!*p_iter) {
+ *p_iter = calloc(1, sizeof **p_iter);
+ if (!*p_iter) return NULL;
+ (*p_iter)->conf = conf;
+ (*p_iter)->conf_item = conf->items;
+ }
+ pom_item_iter *iter = *p_iter;
+ if (iter->conf != conf)
+ fatal_error("%s being called with inconsistent configurations for a single iterator", __func__);
+ if (iter->conf_item >= conf->items + conf->items_count) {
+ free(iter);
+ *p_iter = NULL;
+ return NULL;
+ }
+ iter->item.file = iter->conf_item->file;
+ iter->item.line = iter->conf_item->line;
+ iter->item.key = iter->conf_item->key + conf->prefix_len;
+ iter->item.value = iter->conf_item->value;
+ iter->conf_item++;
+ return &iter->item;
+}
diff --git a/pom.h b/pom.h
index f37c974..50897b8 100644
--- a/pom.h
+++ b/pom.h
@@ -20,7 +20,7 @@
/// distinct configurations in different threads without worry.
/// \mainpage libpom doxygen documentation
-///
+///
/// See \ref pom.h for all types/functions.
#ifndef POM_H_
#define POM_H_
@@ -75,7 +75,7 @@ typedef struct pom_item {
/// The value of the key.
///
/// This pointer is valid until \ref pom_conf_free is called.
- const char *val;
+ const char *value;
/// The file where the key was defined.
///
/// This pointer is valid until \ref pom_conf_free is called.
@@ -331,7 +331,7 @@ char **
pom_conf_get_list(const pom_conf *conf, const char *key)
POM__MUST_USE_R;
-/// Extract section out of POM configuration.
+/// Extract section out of POM configuration. Invalidated if \ref pom_conf_merge is called.
///
/// Specifically, this returns the configuration consisting of all keys
/// prefixed by `section.`, with that prefix removed, and their corresponding
@@ -372,7 +372,7 @@ pom_conf_next_key(const pom_conf *conf, pom_key_iter **iter);
/// Get all key-value pairs in `conf`.
///
/// The first call to this function should be with `*iter = NULL`.
-/// Each time it is called with the same `iter`, it returns the next
+/// Each time it is called with the same `conf` and `iter`, it returns the next
/// key-value pair in `conf`.
/// After all items have been returned (or if an out-of-memory error occurs),
/// `NULL` is returned, any memory
@@ -384,6 +384,8 @@ pom_conf_next_key(const pom_conf *conf, pom_key_iter **iter);
/// you should still call this function repeatedly until you get `NULL`;
/// otherwise memory associated with the iterator may be leaked.
///
+/// The returned pointer is only valid until the next call to \ref pom_conf_next_item.
+///
/// The correct usage for this function is:
///
/// ```C
@@ -409,7 +411,8 @@ pom_conf *
pom_conf_copy(const pom_conf *conf)
POM__MUST_USE_R;
-/// Merge keys from `other` into `conf`, preferring keys in `other`.
+/// Merge keys from `other` into `conf`, preferring keys in `other`. This invalidates
+/// all sections made with \ref pom_conf_section.
void
pom_conf_merge(pom_conf *conf, const pom_conf *other);
diff --git a/pre-commit.sh b/pre-commit.sh
new file mode 100755
index 0000000..6fd5ced
--- /dev/null
+++ b/pre-commit.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+if sed --version | grep -q 'GNU sed'; then
+ sed -i 's/\s\s*$//' pom.c pom.h
+fi
+git add -u