diff options
-rw-r--r-- | examples/read_conf.c | 6 | ||||
-rw-r--r-- | meson.build | 4 | ||||
-rw-r--r-- | pom.c | 242 | ||||
-rw-r--r-- | pom.h | 13 | ||||
-rwxr-xr-x | pre-commit.sh | 5 |
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') @@ -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; +} @@ -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 |