diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | errors.c | 4 | ||||
-rw-r--r-- | examples/read_conf.c | 5 | ||||
-rw-r--r-- | pom.c | 146 | ||||
-rw-r--r-- | pom.h | 44 | ||||
-rw-r--r-- | tests/errors.c | 2 | ||||
-rw-r--r-- | tests/location.c | 4 | ||||
-rw-r--r-- | tests/parsing.c | 4 |
8 files changed, 121 insertions, 90 deletions
@@ -10,7 +10,7 @@ C parser for [POM configuration language](https://pom.computer) int main(void) { pom_error *error; - pom_conf *conf = pom_load_path("conf.pom", &error); + pom_conf *conf = pom_load_path(NULL, "conf.pom", &error); if (!conf) { pom_error_print(error); free(error); @@ -1,8 +1,4 @@ #include <inttypes.h> -#include <assert.h> -#include <string.h> - -static char error_language[32]; enum error_id { ERROR_HEADER, diff --git a/examples/read_conf.c b/examples/read_conf.c index be1f97a..acc3eba 100644 --- a/examples/read_conf.c +++ b/examples/read_conf.c @@ -6,8 +6,9 @@ int main(int argc, char **argv) { pom_error *error; - //pom_set_error_language("fr"); - pom_conf *conf = pom_load_path(argc >= 2 ? argv[1] : "conf.pom", &error); + pom_settings settings = {0}; + strcpy(settings.error_lang, "fr"); + pom_conf *conf = pom_load_path(&settings, argc >= 2 ? argv[1] : "conf.pom", &error); if (!conf) { pom_error_print(error); free(error); @@ -1,7 +1,7 @@ /* TODO: - clean up read_conf.c example -- tests +- interpretation tests */ #include "pom.h" @@ -88,6 +88,7 @@ struct main_conf { // can return this from pom_conf_section when the section is mpety // (so we don't have to store empty sections) pom_conf empty_section; + pom_settings settings; }; struct pom_item_iter { @@ -146,6 +147,7 @@ struct parser { size_t (*read_func)(void *, char *, size_t); void *userdata; pom_error *out_of_memory_error; + const pom_settings *settings; struct { char *array; size_t capacity; @@ -193,24 +195,29 @@ struct parser { #include "errors.c" -void -pom_set_error_language(const char *lang) { - if (lang) - snprintf(error_language, sizeof error_language-1, "%s", lang); - else - strcpy(error_language, "en-US"); +static void * +pom_calloc(const pom_settings *settings, size_t nmemb, size_t sz) { + return settings->calloc(settings->allocator_udata, nmemb, sz); +} +static void * +pom_realloc(const pom_settings *settings, void *ptr, size_t sz) { + return settings->realloc(settings->allocator_udata, ptr, sz); +} +static void +pom_free(const pom_settings *settings, void *ptr) { + return settings->free(settings->allocator_udata, ptr); } - static const char * -get_error_message(enum error_id id) { +get_error_message(const pom_settings *settings, enum error_id id) { assert(id < ERROR_COUNT); const char *const *messages = error_messages_en; + const char *error_lang = settings->error_lang; for (size_t i = 0; i < sizeof error_messages / sizeof error_messages[0]; i++) { const char *lang = error_messages[i].lang; size_t lang_len = strlen(lang); - if (strncmp(error_language, lang, lang_len) == 0 - && (error_language[lang_len] == 0 || error_language[lang_len] == '-')) { + if (strncmp(error_lang, lang, lang_len) == 0 + && (error_lang[lang_len] == 0 || error_lang[lang_len] == '-')) { // language match messages = error_messages[i].messages; break; @@ -240,8 +247,8 @@ fatal_error(const char *fmt, ...) { // Make an error with no next-error. static pom_error * -make_error(const char *file, uint64_t line, enum error_id id, ...) { - const char *fmt = get_error_message(id); +make_error(const pom_settings *settings, const char *file, uint64_t line, enum error_id id, ...) { + const char *fmt = get_error_message(settings, id); va_list args, args_copy; va_start(args, id); va_copy(args_copy, args); @@ -253,7 +260,7 @@ make_error(const char *file, uint64_t line, enum error_id id, ...) { bad_fmt = true; len = strlen(fmt); } - pom_error *err = malloc(sizeof(pom_error) + len * 2 + strlen(file) + 64); + pom_error *err = pom_calloc(settings, sizeof(pom_error) + len * 2 + strlen(file) + 64, 1); if (err) { char *message = (char *)(err + 1); if (bad_fmt) { @@ -271,7 +278,7 @@ make_error(const char *file, uint64_t line, enum error_id id, ...) { #pragma GCC diagnostic ignored "-Wrestrict" #endif sprintf(string, "%s\n%s:%" PRIu64 ": %s\n", - get_error_message(ERROR_HEADER), + get_error_message(settings, ERROR_HEADER), file, line, message); #if !__clang__ && __GNUC__ >= 4 #pragma GCC diagnostic pop @@ -346,7 +353,7 @@ parser_realloc_(struct parser *parser, void *ptr, size_t elem_size, size_t *pcap void **parray = ptr; void *array = *parray; new_capacity = new_capacity * 3 / 2 + 2; - array = realloc(array, new_capacity * elem_size); + array = pom_realloc(parser->settings, array, new_capacity * elem_size); if (!array) { parser_out_of_memory(parser); return false; @@ -405,7 +412,7 @@ static void parser_error(struct parser *parser, enum error_id id, ...) { if (parser->out_of_memory) return; if (parser->errors.count >= 1000) return; // don't bother at this point. - const char *fmt = get_error_message(id); + const char *fmt = get_error_message(parser->settings, id); va_list args, args_copy; va_start(args, id); va_copy(args_copy, args); @@ -852,7 +859,7 @@ parse_line(struct parser *parser) { } static void -set_error(pom_error **error, pom_error *e) { +set_error(const pom_settings *settings, pom_error **error, pom_error *e) { if (error) { *error = e; } else { @@ -874,7 +881,7 @@ conf_free_list_append(struct main_conf *conf, struct to_free *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); + struct to_free *mem = pom_calloc(&conf->settings, 1, sizeof(struct to_free) + nmemb * sz); if (!mem) return NULL; conf_free_list_append(conf, mem); return &mem->data[0]; @@ -885,9 +892,9 @@ 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); + pom_free(&conf->settings, f); } - free(conf); + pom_free(&conf->settings, conf); } static int @@ -1020,11 +1027,12 @@ parser_finish(struct parser *parser) { if (parser->out_of_memory || parser->errors.count) { return NULL; } - struct main_conf *conf = calloc(1, sizeof *conf); + struct main_conf *conf = pom_calloc(parser->settings, 1, sizeof *conf); if (!conf) { parser_out_of_memory(parser); return NULL; } + conf->settings = *parser->settings; conf->empty_section.main = conf; size_t items_count = parser->items.count; struct conf_item *items = conf_calloc(conf, items_count, sizeof(struct conf_item)); @@ -1090,30 +1098,48 @@ parser_finish(struct parser *parser) { return root; } +static void *libc_calloc(void *udata, size_t n, size_t sz) { + return calloc(n, sz); +} + +static void *libc_realloc(void *udata, void *ptr, size_t new_size) { + return realloc(ptr, new_size); +} + +static void libc_free(void *udata, void *ptr) { + free(ptr); +} + pom_conf * -pom_load(const char *filename, +pom_load(const pom_settings *psettings, const char *filename, size_t (*read_func)(void *userdata, char *buf, size_t len), void *userdata, pom_error **error) { if (!filename) fatal_error("%s called with NULL file name", __func__); if (!read_func) fatal_error("%s called with NULL read function", __func__); + pom_settings settings_data = {0}, *settings = &settings_data; + if (psettings) settings_data = *psettings; + if (!settings->calloc) settings->calloc = libc_calloc; + if (!settings->realloc) settings->realloc = libc_realloc; + if (!settings->free) settings->free = libc_free; if (error) *error = NULL; // Start by allocating out-of-memory error, so we can just return // it if we run out of memory. - pom_error *out_of_memory = make_error(filename, 1, ERROR_OUT_OF_MEMORY); + pom_error *out_of_memory = make_error(settings, filename, 1, ERROR_OUT_OF_MEMORY); if (!out_of_memory) return NULL; - char *current_section = calloc(1, 1); + char *current_section = pom_calloc(settings, 1, 1); if (!current_section) { - set_error(error, out_of_memory); + set_error(settings, error, out_of_memory); return NULL; } - struct parser *parser = calloc(1, sizeof *parser); + struct parser *parser = pom_calloc(settings, 1, sizeof *parser); if (!parser) { - free(current_section); - set_error(error, out_of_memory); + pom_free(settings, current_section); + set_error(settings, error, out_of_memory); return NULL; } + parser->settings = settings; parser->filename = filename; parser->out_of_memory_error = out_of_memory; parser->read_func = read_func; @@ -1128,14 +1154,14 @@ pom_load(const char *filename, pom_conf *conf = parser_finish(parser); if (parser->out_of_memory) { - set_error(error, out_of_memory); + set_error(settings, error, out_of_memory); } else if (parser->errors.count) { if (error) { // shouldn't realistically overflow given that we cut off at 1000 errors size_t len = (parser->errors.count + 1) * (sizeof(pom_error) + strlen(filename) + 32) + parser->error_messages.count * 2 + 64; // convert parser_errors to pom_error. - pom_error *errors = malloc(len); + pom_error *errors = pom_calloc(settings, 1, len); if (errors) { char *messages = (char *)(errors + parser->errors.count); memcpy(messages, @@ -1153,7 +1179,7 @@ pom_load(const char *filename, } // create string containing all error messages char *string = strchr(filename, '\0') + 1, *s = string; - sprintf(s, "%s\n", get_error_message(ERROR_HEADER)); + sprintf(s, "%s\n", get_error_message(settings, ERROR_HEADER)); s = strchr(s, 0); for (size_t i = 0; i < parser->errors.count; i++) { const pom_error *e = &errors[i]; @@ -1166,17 +1192,17 @@ pom_load(const char *filename, *error = parser->out_of_memory_error; } } - free(parser->errors.array); - free(parser->error_messages.array); + pom_free(settings, parser->errors.array); + pom_free(settings, parser->error_messages.array); } if (!error || *error != out_of_memory) { - free(out_of_memory); + pom_free(settings, out_of_memory); } - free(parser->line.array); - free(parser->current_section.array); - free(parser->string_data.array); - free(parser->items.array); - free(parser); + pom_free(settings, parser->line.array); + pom_free(settings, parser->current_section.array); + pom_free(settings, parser->string_data.array); + pom_free(settings, parser->items.array); + pom_free(settings, parser); return conf; } @@ -1194,8 +1220,8 @@ read_string(void *vpstring, char *buf, size_t len) { } pom_conf * -pom_load_string(const char *filename, const char *string, pom_error **error) { - return pom_load(filename, read_string, &string, error); +pom_load_string(const pom_settings *settings, const char *filename, const char *string, pom_error **error) { + return pom_load(settings, filename, read_string, &string, error); } #ifndef POM_NO_STDIO @@ -1205,16 +1231,16 @@ read_file(void *file, char *buf, size_t len) { } pom_conf * -pom_load_file(const char *filename, FILE *file, pom_error **error) { +pom_load_file(const pom_settings *settings, 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__); - pom_conf *conf = pom_load(filename, read_file, file, error); + pom_conf *conf = pom_load(settings, filename, read_file, file, error); if (ferror(file)) { if (error) { - free(*error); - *error = make_error(filename, 1, ERROR_FILE_READ); + pom_free(settings, *error); + *error = make_error(settings, filename, 1, ERROR_FILE_READ); } pom_conf_free(conf); conf = NULL; @@ -1223,18 +1249,18 @@ pom_load_file(const char *filename, FILE *file, pom_error **error) { } pom_conf * -pom_load_path(const char *path, pom_error **error) { +pom_load_path(const pom_settings *settings, const char *path, pom_error **error) { if (!path) fatal_error("%s called with NULL file name", __func__); FILE *fp = fopen(path, "rb"); if (!fp) { if (error) { const char *message = strerror(errno); - *error = make_error(path, 1, ERROR_CANT_OPEN_FILE, message); + *error = make_error(settings, path, 1, ERROR_CANT_OPEN_FILE, message); } return NULL; } - pom_conf *conf = pom_load_file(path, fp, error); + pom_conf *conf = pom_load_file(settings, path, fp, error); fclose(fp); return conf; } @@ -1257,7 +1283,7 @@ 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); + *p_iter = pom_calloc(&conf->main->settings, 1, sizeof **p_iter); if (!*p_iter) return NULL; (*p_iter)->conf = conf; (*p_iter)->conf_item = conf->items; @@ -1266,7 +1292,7 @@ pom_conf_next_item(const pom_conf *conf, pom_item_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); + pom_free(&conf->main->settings, iter); *p_iter = NULL; return NULL; } @@ -1283,7 +1309,7 @@ pom_conf_next_unread_key(const pom_conf *conf, pom_unread_key_iter **p_iter) { check_conf(conf); if (!p_iter) fatal_error("NULL iter passed to %s", __func__); if (!*p_iter) { - *p_iter = malloc(sizeof **p_iter); + *p_iter = pom_calloc(&conf->main->settings, 1, sizeof **p_iter); if (!*p_iter) return NULL; (*p_iter)->conf = conf; (*p_iter)->conf_item = conf->items; @@ -1304,7 +1330,7 @@ pom_conf_next_unread_key(const pom_conf *conf, pom_unread_key_iter **p_iter) { return key; } } - free(iter); + pom_free(&conf->main->settings, iter); *p_iter = NULL; return NULL; } @@ -1314,7 +1340,7 @@ pom_conf_next_key(const pom_conf *conf, pom_key_iter **p_iter) { check_conf(conf); if (!p_iter) fatal_error("NULL iter passed to %s", __func__); if (!*p_iter) { - *p_iter = malloc(sizeof **p_iter); + *p_iter = pom_calloc(&conf->main->settings, 1, sizeof **p_iter); if (!*p_iter) return NULL; (*p_iter)->conf = conf; (*p_iter)->conf_item = conf->items; @@ -1328,7 +1354,7 @@ pom_conf_next_key(const pom_conf *conf, pom_key_iter **p_iter) { size_t first_component_len = strcspn(key, "."); if (!iter->prev_key || strncmp(iter->prev_key, key, first_component_len) != 0) { // new first component - char *first_component = realloc(iter->prev_key, first_component_len + 1); + char *first_component = pom_realloc(&conf->main->settings, iter->prev_key, first_component_len + 1); if (!first_component) return NULL; iter->prev_key = first_component; memcpy(first_component, key, first_component_len); @@ -1337,8 +1363,8 @@ pom_conf_next_key(const pom_conf *conf, pom_key_iter **p_iter) { return first_component; } } - free(iter->prev_key); - free(iter); + pom_free(&conf->main->settings, iter->prev_key); + pom_free(&conf->main->settings, iter); *p_iter = NULL; return NULL; } @@ -1538,7 +1564,7 @@ pom_conf_print(const pom_conf *conf) { pom_conf * pom_conf_copy(const pom_conf *conf) { check_conf(conf); - struct main_conf *new_main = calloc(1, sizeof *new_main); + struct main_conf *new_main = pom_calloc(&conf->main->settings, 1, sizeof *new_main); if (!new_main) return NULL; pom_conf *new_root = conf_calloc(new_main, 1, sizeof *new_root); if (!new_root) { @@ -1823,13 +1849,13 @@ pom_conf_get_list(const pom_conf *conf, const char *key) { size_t max_entries = 1; // upper bound on # of entries for (const char *p = value_str; *p; p++) max_entries += *p == ','; - if (max_entries > SIZE_MAX / 16) { + if (max_entries > SIZE_MAX / 4 / sizeof(char *)) { // too many entries (avoid arithmetic overflow) return NULL; } size_t bytes_needed = (max_entries+1) * sizeof (char *) + strlen(value_str) + 1; - char **list = malloc(bytes_needed); + char **list = pom_calloc(&conf->main->settings, 1, bytes_needed); if (!list) return NULL; char **entry = list; char *strings = (char *)(list + max_entries+1); @@ -6,8 +6,6 @@ /// Of course, you should not free or \ref pom_conf_merge into /// a configuration while another thread is using it. /// -/// \ref pom_set_error_language is not thread-safe — see its documentation for more notes. -/// /// Otherwise, libpom is fully thread-safe /// provided that C11 atomics are available /// (`__STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)`). @@ -95,18 +93,26 @@ typedef struct pom_item { uint64_t line; } pom_item; -/// Set language for error messages. -/// -/// This function is **not** thread-safe. Ensure synchronization between calling -/// this and any functions which can return errors. -/// -/// If `lang` is `NULL` or unrecognized, the default of `"en-US"` will be used. -/// -/// Currently supported languages: -/// -/// - `en-US` -void -pom_set_error_language(const char *lang); +/// Settings for libpom. +/// +/// Start by initializing this to zero, +/// then fill out only the fields you need. +typedef struct pom_settings { + /// Data to pass to allocation functions. + void *allocator_udata; + /// calloc function to use for all purposes, or `NULL` to use libc `calloc`. + void *(*calloc)(void *udata, size_t nmemb, size_t sz); + /// realloc function to use for all purposes, or `NULL` to use libc `realloc`. + void *(*realloc)(void *udata, void *ptr, size_t new_sz); + /// free function to use for all purposes, or `NULL` to use libc `free`. + void (*free)(void *udata, void *ptr); + /// Language for error messages. + /// + /// If empty, the default of `"en-US"` will be used. + char error_lang[16]; + /// Reserved for future use. Must be set to 0. + void *reserved[4]; +} pom_settings; /// Load a configuration using a `read`-like function. /// @@ -115,6 +121,8 @@ pom_set_error_language(const char *lang); /// \ref pom_load_path to load from a path, /// and \ref pom_load_string to load from a string. /// +/// `settings` can be `NULL`, in which case the default settings are used. +/// /// On success, a configuration is returned and `*error` is set to `NULL` /// if `error` is not `NULL`. /// @@ -135,7 +143,7 @@ pom_set_error_language(const char *lang); /// `filename` is only used for errors. POM__MUST_USE_L pom_conf * -pom_load(const char *filename, +pom_load(const pom_settings *settings, const char *filename, size_t (*read_func)(void *userdata, char *buf, size_t len), void *userdata, pom_error **error) POM__MUST_USE_R; @@ -153,7 +161,7 @@ POM__MUST_USE_R; /// `filename` is only used for errors. POM__MUST_USE_L pom_conf * -pom_load_file(const char *filename, FILE *file, pom_error **error) +pom_load_file(const pom_settings *settings, const char *filename, FILE *file, pom_error **error) POM__MUST_USE_R; /// Load configuration from a file path. @@ -166,7 +174,7 @@ POM__MUST_USE_R; /// in which case it must be freed with `free`. POM__MUST_USE_L pom_conf * -pom_load_path(const char *path, pom_error **error) +pom_load_path(const pom_settings *settings, const char *path, pom_error **error) POM__MUST_USE_R; #endif @@ -181,7 +189,7 @@ POM__MUST_USE_R; /// `filename` is only used for errors. POM__MUST_USE_L pom_conf * -pom_load_string(const char *filename, const char *string, pom_error **error) +pom_load_string(const pom_settings *settings, const char *filename, const char *string, pom_error **error) POM__MUST_USE_R; /// Get the message of this error. diff --git a/tests/errors.c b/tests/errors.c index 2bcf14c..d1dec96 100644 --- a/tests/errors.c +++ b/tests/errors.c @@ -21,7 +21,7 @@ void test_errors(const char *test_dir) { char *conf_path = malloc(strlen(errors_dir) + strlen(name) + 30); sprintf(conf_path, "%s/errors/%s", test_dir, name); pom_error *error; - pom_conf *conf = pom_load_path(conf_path, &error); + pom_conf *conf = pom_load_path(NULL, conf_path, &error); if (error) { free(error); free(conf_path); diff --git a/tests/location.c b/tests/location.c index 5694f78..372d506 100644 --- a/tests/location.c +++ b/tests/location.c @@ -24,13 +24,13 @@ void test_location(const char *test_dir) { (int)(strlen(name) - strlen(".locations.pom")), name); sprintf(loc_path, "%s/%s", location_dir, name); pom_error *error; - pom_conf *conf = pom_load_path(conf_path, &error); + pom_conf *conf = pom_load_path(NULL, conf_path, &error); if (error) { test_fail("Failed to parse %s\n%s", conf_path, pom_error_to_string(error)); continue; } - pom_conf *loc = pom_load_path(loc_path, &error); + pom_conf *loc = pom_load_path(NULL, loc_path, &error); if (error) { test_fail("Failed to parse %s\n%s", loc_path, pom_error_to_string(error)); diff --git a/tests/parsing.c b/tests/parsing.c index f34107e..c28df0d 100644 --- a/tests/parsing.c +++ b/tests/parsing.c @@ -24,13 +24,13 @@ void test_parsing(const char *test_dir) { (int)(strlen(name) - strlen(".flat.pom")), name); sprintf(flat_path, "%s/parsing/%s", test_dir, name); pom_error *error; - pom_conf *conf = pom_load_path(conf_path, &error); + pom_conf *conf = pom_load_path(NULL, conf_path, &error); if (error) { test_fail("Failed to parse %s\n%s", conf_path, pom_error_to_string(error)); continue; } - pom_conf *flat = pom_load_path(flat_path, &error); + pom_conf *flat = pom_load_path(NULL, flat_path, &error); if (error) { test_fail("Failed to parse %s\n%s", flat_path, pom_error_to_string(error)); |