summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2025-09-14 14:35:56 -0400
committerpommicket <pommicket@gmail.com>2025-09-14 14:41:58 -0400
commit0cc0c89d08994e66fa4fae5c0891b8fa14960e50 (patch)
treedcff3633138512ff7c7b453e54ae12c00f69436a
parentee7e2dbf3d6bc4ef8d4019c666a8960976c1af75 (diff)
Error translations
-rw-r--r--errors.c72
-rw-r--r--examples/conf.pom2
-rw-r--r--examples/read_conf.c7
-rw-r--r--pom.c167
-rw-r--r--pom.h16
5 files changed, 216 insertions, 48 deletions
diff --git a/errors.c b/errors.c
new file mode 100644
index 0000000..2e83f4a
--- /dev/null
+++ b/errors.c
@@ -0,0 +1,72 @@
+#include <inttypes.h>
+#include <assert.h>
+#include <string.h>
+
+static char error_language[32];
+
+enum error_id {
+ ERROR_HEADER,
+ ERROR_OUT_OF_MEMORY,
+ ERROR_CANT_OPEN_FILE,
+ ERROR_FILE_READ,
+ ERROR_INVALID_UTF8,
+ ERROR_ASCII_CONTROL,
+ ERROR_KEY_STARTS_WITH_DOT,
+ ERROR_KEY_ENDS_WITH_DOT,
+ ERROR_KEY_DOT_DOT,
+ ERROR_KEY_INVALID_CHAR,
+ ERROR_INVALID_ESCAPE,
+ ERROR_STRAY_CHARS_AFTER_QUOTED,
+ ERROR_MISMATCHED_SQUARE_BRACKETS,
+ ERROR_INVALID_LINE,
+ ERROR_EMPTY_KEY,
+ ERROR_REDEFINITION,
+ ERROR_COUNT,
+};
+
+static const char *const error_messages_en[ERROR_COUNT] = {
+ [ERROR_HEADER] = "Error:",
+ [ERROR_OUT_OF_MEMORY] = "Out of memory.",
+ [ERROR_CANT_OPEN_FILE] = "Couldn't open file: %s",
+ [ERROR_FILE_READ] = "Couldn't read file",
+ [ERROR_INVALID_UTF8] = "Invalid UTF-8",
+ [ERROR_ASCII_CONTROL] = "Unexpected ASCII control character %d",
+ [ERROR_KEY_STARTS_WITH_DOT] = "Key %s shouldn't begin with .",
+ [ERROR_KEY_ENDS_WITH_DOT] = "Key %s shouldn't end with .",
+ [ERROR_KEY_DOT_DOT] = "Key %s shouldn't contain ..",
+ [ERROR_KEY_INVALID_CHAR] = "Invalid character in key: '%c' (ASCII %d)",
+ [ERROR_INVALID_ESCAPE] = "Invalid escape sequence: \\%.*s",
+ [ERROR_STRAY_CHARS_AFTER_QUOTED] = "Stray characters after closing %c",
+ [ERROR_MISMATCHED_SQUARE_BRACKETS] = "Line starting with [ must end with ]",
+ [ERROR_INVALID_LINE] = "Line should start with [ or contain =",
+ [ERROR_EMPTY_KEY] = "Expected key name before =",
+ [ERROR_REDEFINITION] = "Re-definition of %s (previously defined on line %" PRIu64 ")",
+};
+
+
+static const char *const error_messages_fr[ERROR_COUNT] = {
+ [ERROR_HEADER] = "Erreur:",
+ [ERROR_OUT_OF_MEMORY] = "Mémoire épuisée.",
+ [ERROR_CANT_OPEN_FILE] = "Ne peut pas ouvrir le fichier : %s",
+ [ERROR_FILE_READ] = "Ne peut pas lire le fichier",
+ [ERROR_INVALID_UTF8] = "UTF-8 invalide",
+ [ERROR_ASCII_CONTROL] = "Caractère de contrôle imprévue (ASCII %d)",
+ [ERROR_KEY_STARTS_WITH_DOT] = "Clé %s ne devrait pas commencer par .",
+ [ERROR_KEY_ENDS_WITH_DOT] = "Clé %s ne devrait pas finir en .",
+ [ERROR_KEY_DOT_DOT] = "Clé %s ne devrait pas contenir ..",
+ [ERROR_KEY_INVALID_CHAR] = "Clé clontient une caractère invalide : '%c' (ASCII %d)",
+ [ERROR_INVALID_ESCAPE] = "Séquence d'échappement invalide : \\%.*s",
+ [ERROR_STRAY_CHARS_AFTER_QUOTED] = "Caractère imprévue suivant %c fermant",
+ [ERROR_MISMATCHED_SQUARE_BRACKETS] = "Ligne commençant par [ devrait finir en ]",
+ [ERROR_INVALID_LINE] = "Ligne devrait commençant par [ ou contenir =",
+ [ERROR_EMPTY_KEY] = "Nom de clé devrait précéder =",
+ [ERROR_REDEFINITION] = "Redéfinition de %s (définition précédente à ligne %" PRIu64 ")",
+};
+
+static struct {
+ const char *lang;
+ const char *const *messages;
+} const error_messages[] = {
+ {"en", error_messages_en},
+ {"fr", error_messages_fr},
+};
diff --git a/examples/conf.pom b/examples/conf.pom
index 6b839d1..67890e5 100644
--- a/examples/conf.pom
+++ b/examples/conf.pom
@@ -1,2 +1,2 @@
[number]
-best = "0x127432"
+best j = "-1234567891234567"
diff --git a/examples/read_conf.c b/examples/read_conf.c
index c4cabfc..1bff840 100644
--- a/examples/read_conf.c
+++ b/examples/read_conf.c
@@ -6,15 +6,16 @@
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);
if (!conf) {
pom_error_print(error);
free(error);
return EXIT_FAILURE;
}
- uint64_t value;
- const char *s = pom_conf_get_uint(conf, "number.best", &value);
- printf("%" PRIu64 "\n", value);
+ int64_t value;
+ const char *s = pom_conf_get_int(conf, "number.best", &value);
+ printf("%" PRId64 "\n", value);
if (s) printf(" -> %s\n",s);
pom_conf_print(conf);
pom_conf_free(conf);
diff --git a/pom.c b/pom.c
index 87154e8..ab8e144 100644
--- a/pom.c
+++ b/pom.c
@@ -190,6 +190,36 @@ struct parser {
char buf[4096];
};
+#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 const char *
+get_error_message(enum error_id id) {
+ assert(id < ERROR_COUNT);
+ const char *const *messages = error_messages_en;
+ 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] == '-')) {
+ // language match
+ messages = error_messages[i].messages;
+ break;
+ }
+ }
+ const char *message = messages[id] ? messages[id] : error_messages_en[id];
+ assert(message);
+ return message;
+}
+
#ifdef POM_NO_STDIO
#define fatal_error(...) abort()
#else
@@ -208,11 +238,11 @@ fatal_error(const char *fmt, ...) {
#endif
// Make an error with no next-error.
-static pom_error *make_error(PRINTF_FORMAT_STRING const char *file, uint64_t line, const char *fmt, ...) ATTRIBUTE_PRINTF(3, 4);
static pom_error *
-make_error(const char *file, uint64_t line, const char *fmt, ...) {
+make_error(const char *file, uint64_t line, enum error_id id, ...) {
+ const char *fmt = get_error_message(id);
va_list args, args_copy;
- va_start(args, fmt);
+ va_start(args, id);
va_copy(args_copy, args);
bool bad_fmt = false;
int len = vsnprintf(NULL, 0, fmt, args);
@@ -222,7 +252,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 * 2 + strlen(file) + 48);
+ pom_error *err = malloc(sizeof(pom_error) + len * 2 + strlen(file) + 64);
if (err) {
char *message = (char *)(err + 1);
if (bad_fmt) {
@@ -240,7 +270,9 @@ make_error(const char *file, uint64_t line, const char *fmt, ...) {
#pragma GCC diagnostic ignored "-Wunknown-warning-option"
#pragma GCC diagnostic ignored "-Wrestrict"
#endif
- sprintf(string, "Error:\n%s:%" PRIu64 ": %s\n", file, line, message);
+ sprintf(string, "%s\n%s:%" PRIu64 ": %s\n",
+ get_error_message(ERROR_HEADER),
+ file, line, message);
#if __GNUC__ >= 4
#pragma GCC diagnostic pop
#endif
@@ -369,13 +401,13 @@ parser_append_char(struct parser *parser, char c) {
if (pc) *pc = c;
}
-static void parser_error(struct parser *parser, PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);
static void
-parser_error(struct parser *parser, const char *fmt, ...) {
+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);
va_list args, args_copy;
- va_start(args, fmt);
+ va_start(args, id);
va_copy(args_copy, args);
bool bad_fmt = false;
int error_len = vsnprintf(NULL, 0, fmt, args);
@@ -415,7 +447,7 @@ parser_read_to_buf(struct parser *parser, bool skip_bom) {
// EOF reached.
eof:
if (utf8_state) {
- parser_error(parser, "Invalid UTF-8 (want continuation byte, got EOF).");
+ parser_error(parser, ERROR_INVALID_UTF8);
}
parser->eof = true;
return false;
@@ -428,7 +460,7 @@ parser_read_to_buf(struct parser *parser, bool skip_bom) {
if (read_count < sizeof parser->buf - 1)
parser->short_read = true;
if (parser->leftover_cr && buf[0] != '\n')
- parser_error(parser, "Carriage return with no newline after it.");
+ parser_error(parser, ERROR_ASCII_CONTROL, '\r');
size_t in = 0, out = 0;
uint64_t original_line_number = parser->line_number;
if (skip_bom && read_count >= 3
@@ -447,18 +479,18 @@ parser_read_to_buf(struct parser *parser, bool skip_bom) {
if (in == read_count - 1) {
parser->leftover_cr = true;
} else if (buf[in + 1] != '\n') {
- parser_error(parser, "Carriage return with no newline after it.");
+ parser_error(parser, ERROR_ASCII_CONTROL, '\r');
}
continue;
} else if (byte == '\n') {
parser->line_number++;
} else if (byte >= 0 && byte < 32 && byte != '\t') {
- parser_error(parser, "Illegal control character (ASCII code %d)", byte);
+ parser_error(parser, ERROR_ASCII_CONTROL, byte);
continue;
}
} else if (byte < 0xC2) {
utf8_invalid_start_byte:
- parser_error(parser, "Invalid UTF-8 (invalid start byte 0x%02X)", byte);
+ parser_error(parser, ERROR_INVALID_UTF8);
continue;
} else if (byte < 0xE0) {
// 2-byte sequence
@@ -487,31 +519,31 @@ parser_read_to_buf(struct parser *parser, bool skip_bom) {
} else if (utf8_state == UTF8_STATE_1CONT || utf8_state == UTF8_STATE_2CONT || utf8_state == UTF8_STATE_3CONT) {
utf8_state -= 1;
if ((byte & 0xC0) != 0x80) {
- parser_error(parser, "Invalid UTF-8 (want continuation byte, got 0x%02X)", byte);
+ parser_error(parser, ERROR_INVALID_UTF8);
continue;
}
} else if (utf8_state == UTF8_STATE_2CONT_GTEQ_A0) {
utf8_state = UTF8_STATE_1CONT;
if (byte < 0xA0 || (byte & 0xC0) != 0x80) {
- parser_error(parser, "Invalid UTF-8 (want continuation byte >= 0xA0, got 0x%02X)", byte);
+ parser_error(parser, ERROR_INVALID_UTF8);
continue;
}
} else if (utf8_state == UTF8_STATE_2CONT_LT_A0) {
utf8_state = UTF8_STATE_1CONT;
if (byte >= 0xA0 || (byte & 0xC0) != 0x80) {
- parser_error(parser, "Invalid UTF-8 (want continuation byte < 0xA0, got 0x%02X)", byte);
+ parser_error(parser, ERROR_INVALID_UTF8);
continue;
}
} else if (utf8_state == UTF8_STATE_3CONT_GTEQ_90) {
utf8_state = UTF8_STATE_2CONT;
if (byte < 0x90 || (byte & 0xC0) != 0x80) {
- parser_error(parser, "Invalid UTF-8 (want continuation byte >= 0x90, got 0x%02X)", byte);
+ parser_error(parser, ERROR_INVALID_UTF8);
continue;
}
} else if (utf8_state == UTF8_STATE_3CONT_LT_90) {
utf8_state = UTF8_STATE_2CONT;
if (byte >= 0x90 || (byte & 0xC0) != 0x80) {
- parser_error(parser, "Invalid UTF-8 (want continuation byte < 0x90, got 0x%02X)", byte);
+ parser_error(parser, ERROR_INVALID_UTF8);
continue;
}
} else {
@@ -573,18 +605,20 @@ strip_trailing_accepted_spaces(char *s) {
}
static void
-check_valid_key(struct parser *parser, const char *key) {
+check_if_key_is_valid(struct parser *parser, const char *key) {
uint8_t c;
+ if (key[0] == 0)
+ parser_error(parser, ERROR_EMPTY_KEY);
if (key[0] == '.')
- parser_error(parser, "Key shouldn't begin with .: %s", key);
+ parser_error(parser, ERROR_KEY_STARTS_WITH_DOT, key);
for (size_t i = 0; (c = key[i]); i++) {
bool bad = false;
if (c < 64) {
if (c == '.') {
if (key[i+1] == 0) {
- parser_error(parser, "Key shouldn't end with .: %s", key);
+ parser_error(parser, ERROR_KEY_ENDS_WITH_DOT, key);
} else if (key[i+1] == '.') {
- parser_error(parser, "Key shouldn't contain ..: %s", key);
+ parser_error(parser, ERROR_KEY_DOT_DOT, key);
}
}
// bitmask of disallowed ASCII characters 0-63
@@ -594,7 +628,7 @@ check_valid_key(struct parser *parser, const char *key) {
bad = (0xfc0000017c000001U >> c) & 1;
}
if (bad) {
- parser_error(parser, "Invalid character in key: '%c' (ASCII %d)", c, c);
+ parser_error(parser, ERROR_KEY_INVALID_CHAR, c, c);
}
}
}
@@ -616,7 +650,7 @@ parse_escape_sequence(struct parser *parser, const char **p_str) {
switch (*str++) {
invalid_sequence: {
int len = (int)(str - *p_str);
- parser_error(parser, "Invalid escape sequence: \\%.*s", len, *p_str);
+ parser_error(parser, ERROR_INVALID_ESCAPE, len, *p_str);
return;
} break;
case 'n':
@@ -704,7 +738,7 @@ parse_quoted_value(struct parser *parser, const char *first_line) {
while ((c = *line++)) {
if (c != ' ' && c != '\t') {
parser_error(parser,
- "Stray characters after closing %c",
+ ERROR_STRAY_CHARS_AFTER_QUOTED,
delimiter);
}
}
@@ -738,7 +772,7 @@ parse_line(struct parser *parser) {
strip_trailing_accepted_spaces(line);
size_t len = strlen(line);
if (line[len-1] != ']') {
- parser_error(parser, "Line starting with [ must end with ]");
+ parser_error(parser, ERROR_MISMATCHED_SQUARE_BRACKETS);
return;
}
line += 1;
@@ -750,7 +784,7 @@ parse_line(struct parser *parser) {
current_section[len] = 0;
parser->current_section.len = len;
if (len)
- check_valid_key(parser, current_section);
+ check_if_key_is_valid(parser, current_section);
return;
}
size_t equals_idx;
@@ -760,12 +794,12 @@ parse_line(struct parser *parser) {
break;
}
if (line[i] == 0) {
- parser_error(parser, "Line should start with [ or contain an =");
+ parser_error(parser, ERROR_INVALID_LINE);
return;
}
}
if (equals_idx == 0) {
- parser_error(parser, "Expected key name before =");
+ parser_error(parser, ERROR_EMPTY_KEY);
return;
}
size_t key_idx = parser->string_data.count;
@@ -781,7 +815,7 @@ parse_line(struct parser *parser) {
memcpy(p, line, equals_idx);
p[equals_idx] = 0;
strip_trailing_accepted_spaces(p);
- check_valid_key(parser, key);
+ check_if_key_is_valid(parser, key);
}
size_t value_start_idx = equals_idx + 1;
while (line[value_start_idx] == ' ' || line[value_start_idx] == '\t')
@@ -1025,8 +1059,7 @@ parser_finish(struct parser *parser) {
max_line = item1->line;
}
parser->line_number = max_line;
- parser_error(parser, "Re-definition of %s (previously defined on line %" PRIu64 ")",
- item1->key, min_line);
+ parser_error(parser, ERROR_REDEFINITION, item1->key, min_line);
}
if (parser->errors.count) {
conf_free(conf);
@@ -1055,7 +1088,7 @@ pom_load(const char *filename,
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, "Out of memory.");
+ pom_error *out_of_memory = make_error(filename, 1, ERROR_OUT_OF_MEMORY);
if (!out_of_memory) return NULL;
char *current_section = calloc(1, 1);
if (!current_section) {
@@ -1087,7 +1120,7 @@ pom_load(const char *filename,
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 + 16;
+ + parser->error_messages.count * 2 + 64;
// convert parser_errors to pom_error.
pom_error *errors = malloc(len);
if (errors) {
@@ -1107,7 +1140,7 @@ pom_load(const char *filename,
}
// create string containing all error messages
char *string = strchr(filename, '\0') + 1, *s = string;
- strcpy(s, "Error:\n");
+ sprintf(s, "%s\n", get_error_message(ERROR_HEADER));
s = strchr(s, 0);
for (size_t i = 0; i < parser->errors.count; i++) {
const pom_error *e = &errors[i];
@@ -1164,7 +1197,16 @@ pom_load_file(const char *filename, FILE *file, pom_error **error) {
fatal_error("%s called with NULL file name", __func__);
if (!file)
fatal_error("%s called with NULL file", __func__);
- return pom_load(filename, read_file, file, error);
+ pom_conf *conf = pom_load(filename, read_file, file, error);
+ if (ferror(file)) {
+ if (error) {
+ free(*error);
+ *error = make_error(filename, 1, ERROR_FILE_READ);
+ }
+ pom_conf_free(conf);
+ conf = NULL;
+ }
+ return conf;
}
pom_conf *
@@ -1175,7 +1217,7 @@ pom_load_path(const char *path, pom_error **error) {
if (!fp) {
if (error) {
const char *message = strerror(errno);
- *error = make_error(path, 1, "Couldn't open file: %s", message);
+ *error = make_error(path, 1, ERROR_CANT_OPEN_FILE, message);
}
return NULL;
}
@@ -1547,10 +1589,15 @@ parse_uint(const char *s, uint64_t *val) {
if (*s == '0' && (s[1] == 'x' || s[1] == 'X')) {
// hexadecimal
for (size_t i = 2; s[i]; i++) {
- if (parse_hex_digit(s[i]) == -1)
+ int digit = parse_hex_digit(s[i]);
+ if (digit == -1) return false;
+ value <<= 4;
+ value |= digit;
+ if (value >> 53) {
+ // too big
return false;
+ }
}
- value = (uint64_t) strtoull(s, NULL, 16);
} else if (*s == '0') {
if (s[1] != 0) {
// leading zero
@@ -1558,10 +1605,16 @@ parse_uint(const char *s, uint64_t *val) {
}
} else {
for (size_t i = 0; s[i]; i++) {
- if (s[i] < '0' || s[i] > '9')
+ int digit = s[i] - '0';
+ if (digit < 0 || digit > 9)
+ return false;
+ value *= 10;
+ value += digit;
+ if (value >> 53) {
+ // too big
return false;
+ }
}
- value = (uint64_t) strtoull(s, NULL, 10);
}
if (value >> 53) {
// too big!
@@ -1571,6 +1624,21 @@ parse_uint(const char *s, uint64_t *val) {
return true;
}
+static bool
+parse_int(const char *s, int64_t *val) {
+ int sign = 1;
+ if (*s == '-') {
+ sign = -1;
+ s++;
+ if (*s == '+')
+ return false;
+ }
+ uint64_t absval;
+ if (!parse_uint(s, &absval)) return false;
+ *val = (int64_t)absval * sign;
+ return true;
+}
+
const char *
pom_conf_get_uint(const pom_conf *conf, const char *key, uint64_t *value_uint) {
check_conf(conf);
@@ -1578,10 +1646,23 @@ pom_conf_get_uint(const pom_conf *conf, const char *key, uint64_t *value_uint) {
if (!value_uint) fatal_error("NULL value passed to %s", __func__);
*value_uint = 0;
const char *value_str = pom_conf_get(conf, key);
- if (!value_str) {
+ if (!value_str)
return "";
- }
if (parse_uint(value_str, value_uint))
return NULL;
return value_str;
}
+
+const char *
+pom_conf_get_int(const pom_conf *conf, const char *key, int64_t *value_int) {
+ check_conf(conf);
+ if (!key) fatal_error("NULL key passed to %s", __func__);
+ if (!value_int) fatal_error("NULL value passed to %s", __func__);
+ *value_int = 0;
+ const char *value_str = pom_conf_get(conf, key);
+ if (!value_str)
+ return "";
+ if (parse_int(value_str, value_int))
+ return NULL;
+ return value_str;
+}
diff --git a/pom.h b/pom.h
index 5635316..79bdcd7 100644
--- a/pom.h
+++ b/pom.h
@@ -6,7 +6,9 @@
/// Of course, you should not free or \ref pom_conf_merge into
/// a configuration while another thread is using it.
///
-/// Other than that, all these functions are fully thread-safe
+/// \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__)`).
/// But beware of race conditions when using \ref pom_conf_next_unread_key
@@ -93,6 +95,18 @@ 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);
+
/// Load a configuration using a `read`-like function.
///
/// Most of the time, you won't need this function: