101 lines
4.3 KiB
C
101 lines
4.3 KiB
C
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
|
// SPDX license identifier: MIT
|
|
|
|
#ifndef __DS_ERROR_H__
|
|
#define __DS_ERROR_H__
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
/* It's pretty important that error messages get through, so we reserve 32kiB
|
|
* which we can free if malloc fails. Then we should hopefully at least have
|
|
* enough memory to allocate for the error message. */
|
|
#define ERROR_RESERVATION_SIZE 32768
|
|
extern void *error_reserved_for_error;
|
|
|
|
/* Some useful shortcuts for constructing errors. */
|
|
|
|
/* Essentially just a "no error" error. */
|
|
#define OK() (Error){ .kind = ErrorNone }
|
|
/* Use when malloc fails, for example. */
|
|
#define ERROR_OUT_OF_MEMORY() (Error){ .kind = ErrorOutOfMemory }
|
|
/* Use for string constants ("this is a string constant"). */
|
|
#define ERROR_STRING(_str) (Error){ .kind = ErrorString, .str = (char *)_str }
|
|
/* Use for heap-allocated strings (e.g. by malloc(), strdup() etc.). */
|
|
#define ERROR_HEAP_STRING(_str) (Error){ .kind = ErrorString, .str = _str, .str_on_heap = true }
|
|
|
|
/* Embed the current file and line information in the error message. */
|
|
#define ERROR_OUT_OF_MEMORY_HERE() (Error){ .kind = ErrorOutOfMemory, .has_location = true, .file = __FILE__, .line = __LINE__ }
|
|
#define ERROR_STRING_HERE(_str) (Error){ .kind = ErrorString, .str = (char *)_str, .has_location = true, .file = __FILE__, .line = __LINE__ }
|
|
#define ERROR_HEAP_STRING_HERE(_str) (Error){ .kind = ErrorString, .str = _str, .str_on_heap = true, .has_location = true, .file = __FILE__, .line = __LINE__ }
|
|
|
|
/* Embed custom file and line information in the error message. */
|
|
#define ERROR_OUT_OF_MEMORY_LOCATION(_file, _line) (Error){ .kind = ErrorOutOfMemory, .has_location = true, .file = _file, .line = _line }
|
|
#define ERROR_STRING_LOCATION(_file, _line, _str) (Error){ .kind = ErrorString, .str = (char *)_str, .has_location = true, .file = _file, .line = _line }
|
|
#define ERROR_HEAP_STRING_LOCATION(_file, _line, _str) (Error){ .kind = ErrorString, .str = _str, .str_on_heap = true, .has_location = true, .file = _file, .line = _line }
|
|
|
|
/* Append one error to another (will show as "error1: error2" in the error message). */
|
|
#define ERROR_NESTED(base, _annex) (Error){ .kind = base.kind, .str = base.str, .has_annex = true, .annex = _error_heapify(_annex) }
|
|
|
|
/* Write the current file and line to an error and return it. */
|
|
#define ERROR_HEREIFY(_err) _error_hereify(__FILE__, __LINE__, _err)
|
|
|
|
/* Propagates any potential errors. Use the otherwise field for any uninitialization code. */
|
|
#define TRY(expr, otherwise) { Error _err = expr; if (_err.kind != ErrorNone) { otherwise; return _err; } }
|
|
|
|
/* If x is an error, this prints a nice error message and then exits. */
|
|
#define ERROR_ASSERT(x) /* use with moderation */ { \
|
|
Error _err = x; \
|
|
if (_err.kind != ErrorNone) { \
|
|
if (!_err.has_location) { \
|
|
_err.has_location = true; \
|
|
_err.file = __FILE__; \
|
|
_err.line = __LINE__; \
|
|
} \
|
|
char _buf[512]; error_to_string(_buf, 512, _err, true); \
|
|
fprintf(stderr, "Fatal error: %s\n", _buf); \
|
|
exit(1); \
|
|
} \
|
|
}
|
|
|
|
typedef enum {
|
|
ErrorNone = 0,
|
|
ErrorOutOfMemory,
|
|
ErrorString,
|
|
} ErrorKind;
|
|
|
|
typedef struct Error {
|
|
ErrorKind kind;
|
|
char *str;
|
|
struct Error *annex;
|
|
const char *file;
|
|
size_t line;
|
|
bool str_on_heap;
|
|
bool has_annex;
|
|
bool has_location;
|
|
} Error;
|
|
|
|
/* Returns true if the given error structure actually contains a real error. */
|
|
static inline bool error_is(Error e) { return e.kind != ErrorNone; }
|
|
/* Same as error_is but takes a pointer and does NULL checking. */
|
|
static inline bool error_ptr_is(Error *restrict e) { return e != NULL && e->kind != ErrorNone; }
|
|
|
|
/* Call this at the beginning of any program that makes use of any of the more
|
|
* advanced error features (those which involve heap allocation). */
|
|
void error_init();
|
|
/* Call this at the end of any program that used error_init() to clean up. */
|
|
void error_term();
|
|
|
|
/* Writes an error to a string. It is recommended to use ds/fmt.h in most cases, e.g.:
|
|
#include <ds/fmt.h>;
|
|
fmt("%{Error:destroy}", err);
|
|
*/
|
|
size_t error_to_string(char *restrict buf, size_t size, Error e, bool destroy);
|
|
|
|
Error *_error_heapify(Error e);
|
|
Error _error_hereify(const char *restrict file, size_t line, Error e);
|
|
|
|
#endif
|