From c6b3fde6d4d82c0f72c6d499398d784342c449d8 Mon Sep 17 00:00:00 2001 From: Madeorsk Date: Sun, 12 Feb 2023 17:38:57 +0100 Subject: [PATCH] Add an error system, with its function result system that can carry a value and an error at the same time. --- include/ksr/errors.h | 150 +++++++++++++++++++++++++++++++++++++++++++ meson.build | 2 + tests/ksrerrors.c | 101 +++++++++++++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 include/ksr/errors.h create mode 100644 tests/ksrerrors.c diff --git a/include/ksr/errors.h b/include/ksr/errors.h new file mode 100644 index 0000000..8a01016 --- /dev/null +++ b/include/ksr/errors.h @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include + +/** + * Type of an error code. + */ +typedef int ksrerrorcode_t; + +/** + * Structure of an error. + */ +typedef struct { + ksrerrorcode_t code; + char *message; + void *data; +} ksrerror; + +#define KSRERROR_NOERROR_CODE 0 + +/** + * An error struct that indicates no error. + */ +#define ksrerror_none ((ksrerror) { KSRERROR_NOERROR_CODE, NULL, NULL }) +/** + * Create a new error structure with given code, message and custom error data. + */ +#define ksrerror_new_d(error_code, error_message, error_data) ((ksrerror) { error_code, strdup(error_message), error_data }) +/** + * Create a new error structure with given code and message. + */ +#define ksrerror_new(error_code, error_message) ksrerror_new_d(error_code, error_message, NULL) + +/** + * Throw a new error structure with given code, message and custom error data. + */ +#define ksrerror_throw_d(error_code, error_message, error_data) return ksrerror_new_d(error_code, error_message, error_data) +/** + * Throw a new error structure with given code and message. + */ +#define ksrerror_throw(error_code, error_message) return ksrerror_new(error_code, error_message) + +/** + * Free an error and its custom data, with the given free function. + */ +static inline void ksrerror_free_d(ksrerror error, void (*free_data_func)(void*)) +{ + if (error.message) free(error.message); + if (error.data) free_data_func(error.data); +} +/** + * Free an error without custom data. + */ +static inline void ksrerror_free(ksrerror error) +{ + ksrerror_free_d(error, free); +} + +/** + * Ignore an error and free its custom data. + */ +#define ksrerror_ignore_d(error, free_data_func) ksrerror_free_d(error, free_data_func) +/** + * Ignore an error without custom data. + */ +#define ksrerror_ignore(error) ksrerror_free(error) + +/** + * Check if the structure indicates an error. + * Return true if there is no error, false if there is one. + */ +static inline bool ksrerror_check(ksrerror error) +{ + return error.code == KSRERROR_NOERROR_CODE; +} + +/** + * Propagate the error, if there is one. + */ +#define ksrerror_propagate(error) { ksrerror __ksrerror__cache__ = error; if (!ksrerror_check(__ksrerror__cache__)) return __ksrerror__cache__; } + +/** + * Panic! Show error message and exit app. + */ +#define ksrerror_panic(error) { \ + ksrerror __ksrerror__cache__ = error; \ + if (__ksrerror__cache__.code != KSRERROR_NOERROR_CODE) { \ + char *__ksrerror__error_message__ = malloc(5 + 64 + strlen(__ksrerror__cache__.message) + 1); \ + sprintf(__ksrerror__error_message__, "code %d: %s", __ksrerror__cache__.code, __ksrerror__cache__.message); \ + ksrlog_error(__ksrerror__error_message__); exit(__ksrerror__cache__.code); \ +} } + + + +/** + * Structure of a result that can be an error. + * This structure can be used as a return type for functions that can encounter an error. + */ +typedef struct { + void *result; + ksrerror error; +} ksresult; + +/** + * Define a result of a specific type. + */ +#define ksresult_of_type(result_type) ksresult +/** + * Create a new result. + */ +#define ksresult_new(_result) ((ksresult) { _result, ksrerror_none }) +/** + * Create a result that contains an error. + */ +#define ksresult_new_error(error) ((ksresult) { NULL, (error) }) +/** + * Throw an error as a result. + */ +#define ksresult_throw(error) return ksresult_new_error(error) + +/** + * Try to get a result and put it in the variable called varname. + * You must specify the type of the result you want to get. + * If there is an error, it is thrown as a result. You cannot use this function if your function does not return a ksresult. + */ +#define ksresult_get(varname, result_type, _result) NULL; { \ + ksresult __ksresult_cache__ = (_result); \ + if (!ksresult_check(__ksresult_cache__)) return __ksresult_cache__; \ + varname = ((result_type) __ksresult_cache__.result); \ +} +/** + * Try to get a result while ignoring the error if there is one. + * You must specify the type of the result you want to get. + * Directly return the result data. + */ +#define ksresult_get_ignore(result_type, _result) ((result_type) (_result).result) +/** + * Get the error of a result. + */ +#define ksresult_error(_result) (_result).error +/** + * Check if there is an error in the given result. + */ +#define ksresult_check(_result) ksrerror_check(ksresult_error(_result)) + + diff --git a/meson.build b/meson.build index f6e1d75..6f9730b 100644 --- a/meson.build +++ b/meson.build @@ -35,6 +35,7 @@ test_ksregex = executable('test_ksregex', [ 'tests/ksregex.c' ], dependencies : test_ksrstring = executable('test_ksrstring', [ 'tests/ksrstring.c' ], dependencies : deps, include_directories : include_dirs, install : false) test_ksrfiles = executable('test_ksrfiles', [ 'tests/ksrfiles.c' ], dependencies : deps, include_directories : include_dirs, install : false) test_ksrlogging = executable('test_ksrlogging', [ 'tests/ksrlogging.c' ], dependencies : deps, include_directories : include_dirs, install : false) +test_ksrerrors = executable('test_ksrerrors', [ 'tests/ksrerrors.c' ], dependencies : deps, include_directories : include_dirs, install : false) # tests. test('ksrarrays', test_ksrarrays) @@ -44,3 +45,4 @@ test('ksregex', test_ksregex) test('ksrfiles', test_ksrfiles) test('ksrstring', test_ksrstring) test('ksrlogging', test_ksrlogging) +test('ksrerrors', test_ksrerrors) diff --git a/tests/ksrerrors.c b/tests/ksrerrors.c new file mode 100644 index 0000000..6d51891 --- /dev/null +++ b/tests/ksrerrors.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include + +ksrerror err_throwsomething(int var) +{ + if (var == 1) + // we consider that if the value of var is one, there is an error. + ksrerror_throw(1, "something."); + + return ksrerror_none; // no error happened. +} + +ksrerror err_propagation(void) +{ + // calling throwsomething and propagating error if there is one. + ksrerror_propagate(err_throwsomething(1)); + + return ksrerror_none; // no error happened. +} + +ksresult_of_type(char*) res_returnok(void) +{ + // creating a new result and returning it. + return ksresult_new("testing something."); +} + +ksresult_of_type(char*) res_returnerr(int var) +{ + if (var != 10) + // we consider that if the value of var is ten, there is an error. + ksresult_throw(ksrerror_new(6, "test error.")); + + // creating a new result and returning it. + return ksresult_new("another test."); +} + +ksresult_of_type(int*) res_propagate_err(void) +{ + int *returnval = malloc(sizeof(int)); + (*returnval) = 5; + + char *test = ksresult_get(test, char*, res_returnerr(*returnval)); + printf("propagation failed? %s\n", test); + + return ksresult_new(returnval); +} + +int main(void) +{ + /* * Testing simple errors * */ + + + // testing that ksrerror_none is not an error. + assert(ksrerror_check(ksrerror_none)); + + // testing that an error is correctly thrown. + assert(ksrerror_check(err_throwsomething(0))); + assert(!ksrerror_check(err_throwsomething(1))); + ksrerror_ignore(err_throwsomething(1)); + + // testing that propagation works. + assert(!ksrerror_check(err_propagation())); + assert(err_propagation().code == 1); + assert(strcmp(err_propagation().message, "something.") == 0); + + // testing panic when no error happen. + ksrerror_panic(err_throwsomething(0)); + + + /* * Testing ksresults * */ + + + // test1: no error, valid return value. + ksresult res1 = res_returnok(); + assert(ksresult_check(res1)); // checking that there is no error. + assert(strcmp(ksresult_get_ignore(char*, res1), "testing something.") == 0); + + + // test2: error. + ksresult res2 = res_returnerr(2); + assert(!ksresult_check(res2)); // checking that there is an error. + // checking that the error has been correctly passed. + assert(ksresult_error(res2).code == 6); + assert(strcmp(ksresult_error(res2).message, "test error.") == 0); + ksrerror_free(res2.error); // free the thrown error. + + + // test3: error propagation. + ksresult res3 = res_propagate_err(); + assert(!ksresult_check(res3)); // checking that there is an error. + // checking that the error has been correctly passed. + assert(ksresult_error(res3).code == 6); + assert(strcmp(ksresult_error(res3).message, "test error.") == 0); + ksrerror_free(res3.error); // free the thrown error. + + + return 0; +}