From 0e220eaa56d5b1947e6bead8e5695446ab102fba Mon Sep 17 00:00:00 2001 From: pommicket Date: Sun, 14 Sep 2025 01:26:00 -0400 Subject: simplify error-to-string interface --- examples/conf.pom | 4 ++-- examples/read_conf.c | 7 +----- pom.c | 66 ++++++++++++++++++++++++++++------------------------ pom.h | 24 +++++++++++-------- 4 files changed, 53 insertions(+), 48 deletions(-) diff --git a/examples/conf.pom b/examples/conf.pom index 0edc83f..ed37ed9 100644 --- a/examples/conf.pom +++ b/examples/conf.pom @@ -1,7 +1,7 @@ -[number] +[number]p one = 1 two = 2x"\? -three = "3 +three j= "3 is the best" diff --git a/examples/read_conf.c b/examples/read_conf.c index 54a1e86..18802e3 100644 --- a/examples/read_conf.c +++ b/examples/read_conf.c @@ -8,12 +8,7 @@ int main(int argc, char **argv) { pom_error *error; pom_conf *conf = pom_load_path(argc >= 2 ? argv[1] : "conf.pom", &error); if (!conf) { - 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); + pom_error_print(error); free(error); return EXIT_FAILURE; } diff --git a/pom.c b/pom.c index 01d9efb..906e970 100644 --- a/pom.c +++ b/pom.c @@ -35,6 +35,9 @@ struct pom_error { const char *file; uint64_t line; const char *message; + // only set for first error in error list. + // return value of pom_error_to_string + const char *string; }; struct conf_item { @@ -219,7 +222,7 @@ make_error(const char *file, uint64_t line, const char *fmt, ...) { bad_fmt = true; len = strlen(fmt); } - pom_error *err = malloc(sizeof(pom_error) + len + 1); + pom_error *err = malloc(sizeof(pom_error) + len * 2 + strlen(file) + 48); if (err) { char *message = (char *)(err + 1); if (bad_fmt) { @@ -230,6 +233,17 @@ make_error(const char *file, uint64_t line, const char *fmt, ...) { err->file = file; err->line = line; err->message = message; + char *string = strchr(message, '\0') + 1; + // no, clang, string will not overlap with message. + #if __GNUC__ >= 4 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wrestrict" + #endif + sprintf(string, "Error:\n%s:%" PRIu64 ": %s\n", file, line, message); + #if __GNUC__ >= 4 + #pragma GCC diagnostic pop + #endif + err->string = string; err->next = NULL; } va_end(args_copy); @@ -265,15 +279,8 @@ pom_error_message(const pom_error *error) { #ifndef POM_NO_STDIO void -pom_error_print(const pom_error *error) { - if (!error) { - fprintf(stderr, "No error.\n"); - return; - } - fprintf(stderr, "Error:\n"); - for (; error; error = pom_error_next(error)) { - fprintf(stderr, "%s:%" PRIu64 ": %s\n", error->file, error->line, error->message); - } +pom_error_print(pom_error *error) { + fputs(pom_error_to_string(error), stderr); } #endif @@ -282,26 +289,13 @@ parser_out_of_memory(struct parser *parser) { parser->out_of_memory = true; } -char * +const 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); + if (error) { + return error->string; + } else { + return "No error.\n"; } - 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; @@ -1090,8 +1084,9 @@ pom_load(const char *filename, set_error(error, out_of_memory); } else if (parser->errors.count) { if (error) { - // shouldn't overflow - size_t len = parser->errors.count * sizeof(pom_error) + parser->error_messages.count + strlen(filename) + 1; + // 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 + 16; // convert parser_errors to pom_error. pom_error *errors = malloc(len); if (errors) { @@ -1107,7 +1102,18 @@ pom_load(const char *filename, errors[i].line = parser_error->line; errors[i].message = messages + parser_error->message; errors[i].next = i == parser->errors.count - 1 ? NULL : &errors[i+1]; + errors[i].string = NULL; + } + // create string containing all error messages + char *string = strchr(filename, '\0') + 1, *s = string; + strcpy(s, "Error:\n"); + s = strchr(s, 0); + for (size_t i = 0; i < parser->errors.count; i++) { + const pom_error *e = &errors[i]; + sprintf(s, "%s:%" PRIu64 ": %s\n", e->file, e->line, e->message); + s = strchr(s, 0); } + errors->string = string; *error = errors; } else { *error = parser->out_of_memory_error; diff --git a/pom.h b/pom.h index ea38a3d..9adb156 100644 --- a/pom.h +++ b/pom.h @@ -90,6 +90,11 @@ typedef struct pom_item { /// Load a configuration using a `read`-like function. /// +/// Most of the time, you won't need this function: +/// use \ref pom_load_file to load from a `FILE *`, +/// \ref pom_load_path to load from a path, +/// and \ref pom_load_string to load from a string. +/// /// On success, a configuration is returned and `*error` is set to `NULL` /// if `error` is not `NULL`. /// @@ -99,11 +104,6 @@ typedef struct pom_item { /// there isn’t even enough memory for an out-of-memory /// error, `NULL` is returned and `*error` is set to `NULL`. /// -/// Most of the time, you won't need this function: -/// use \ref pom_load_file to load from a `FILE *`, -/// \ref pom_load_path to load from a path, -/// and \ref pom_load_string to load from a string. -/// /// `read_func` will be passed the `userdata` pointer passed to this function, /// a buffer, and the length of that buffer (which will be nonzero). /// It must fill out the buffer as much as possible, @@ -121,7 +121,7 @@ pom_load(const char *filename, POM__MUST_USE_R; #ifndef POM_NO_STDIO -/// Load configuration from a `FILE *`, which should be opened in binary mode. +/// Load configuration from a `FILE *` opened in read-binary mode. /// /// On success, a configuration is returned and `*error` is set to `NULL` /// if `error` is not `NULL`. @@ -187,7 +187,11 @@ pom_error_file(const pom_error *error); uint64_t pom_error_line(const pom_error *error); -/// Get next error in error list. +/// Get next error in error list, or `NULL` if this is the last error. +/// +/// You can only call \ref pom_error_message, \ref pom_error_file, and \ref pom_error_line +/// on the returned error, not \ref pom_error_print or \ref pom_error_to_string. +/// (This is enforced with `const`-ness.) const pom_error * pom_error_next(const pom_error *error); @@ -196,14 +200,14 @@ pom_error_next(const pom_error *error); /// /// Includes every error in an error list (see \ref pom_error_next). void -pom_error_print(const pom_error *error); +pom_error_print(pom_error *error); #endif -/// Convert error to string. Return value must be `free()`d. +/// Convert error to string. Return value is valid until `error` is `free()`’d. /// /// Includes every error in an error list (see \ref pom_error_next). POM__MUST_USE_L -char * +const char * pom_error_to_string(pom_error *error) POM__MUST_USE_R; -- cgit v1.2.3