XError ====== This is a small C library to help with error handling for error reporting. This library helps you deal with errors by capturing conditions which indicate that an error occured. These conditions are expressions and they're captured inside macro applications. One of these macros is the ErrLt0 macro, it checks if its given expression is less than 0, and if so it understands that as an error signal. For example: ErrLt0(SDL_Init(SDL_INIT_VIDEO), SDL_GetError()); This conceptually translates to: int test = SDL_Init(SDL_INIT_VIDEO); if (test < 0) { return test; } This "" will be explained later. The header XError.h basically defines a set of foundations types and functions to report errors and a bunch of macros which help you make use of these underlying features. Basically, to report an error you use the XERR_PushError function: int XERR_PushError(const char *msg, int line, const char *file_name, const char *func_name, const char *code); It copies all the content given to it to an internally managed structure of XERR_Error elements. It keeps its own internal XERR_ErrorSequence. struct XERR_String { char *data; int len; }; struct XERR_Error { int line; struct XERR_String msg, file, func, code; }; struct XERR_ErrorSequence { size_t count; struct XERR_Error *errors; }; The "" piece above makes use of the preprocessor to fill in the form for you by calling XERR_PushError using facilities like __LINE__, __func__ and so on: #define ErrLt0(EXPR, MSG) \ do { \ int X_ERROR_test = (EXPR); \ if (X_ERROR_test < 0) { \ XERR_PushError((MSG), __LINE__, __FILE__, __func__, # EXPR); \ return X_ERROR_test; \ } \ } while (0) XERR_PushError allows you to pass it null as the message, in which case it'll simply understand that there is no error message accompaning the issued error info. This is useful when you just want to pass on an error, but still build a track of calls: int InitSdl(void) { /* ... */ // Issues an error with its own message. ErrLt0(SDL_Init(SDL_INIT_VIDEO), SDL_GetError()); /* ... */ } int Init(void) { /* ... */ // Only propagates the error. ErrLt0(InitSdl(), 0); /* ... */ } A possibility is to use the PErrLt0 instead, which will pass the 0 for you. #define PErrLt0(EXPR) ErrLt0(EXPR, 0) This way, you can add macro applications to your code (which, yes, can make things messier), and have a sequence of error information structures be built for you whenever there is an error. When an error happens and you want to handle it, all you have to do is just not propagate it. Get the error return code and write some logic to deal with it. This library doesn't attempt to give you a general way for you to handle errors. What it gives you is a way to handle reporting errors when they happen. To report an error, you copy the internal error structures kept by the XError library and go through it yourself to generate your error report. For example: static inline const char * FmtMessage(const char *msg, const char *alt) { return msg ? (*msg ? msg : alt) : alt; } void ReportError(void) { struct XERR_ErrorSequence err_seq = XERR_CopyErrors(); const char *fmt = "\t%s: L%d: %s: %s\n" "\t\t%s\n"; fputs("Error! Aborting program.\n" "Bread Crumbs:\n" "=============\n", stderr); for (size_t i = 0; i < err_seq.count; i++) { struct XERR_Error *err = err_seq.errors + i; fprintf(stderr, fmt, FmtMessage(err->file.data, "(missing file name)"), p->line, FmtMessage(err->func.data, "(missing function name)"), FmtMessage(err->msg.data, "(missing message)"), FmtMessage(err->code.data, "(missing code)")); } fmt = "\n\tSDL_GetError() => %s\n" "\tIMG_GetError() => %s\n" "\tTTF_GetError() => %s\n" "\tMix_GetError() => %s\n"; fprintf(stderr, fmt, FmtMessage(SDL_GetError(), "(empty)"), FmtMessage(IMG_GetError(), "(empty)"), FmtMessage(TTF_GetError(), "(empty)"), FmtMessage(Mix_GetError(), "(empty)")); XERR_FreeErrors(err_seq); XERR_ClearInternalSequence(); } The XERR_ErrorSequence is a small struct (pointer and a size variable), which is why it's passed by value everywhere. XERR_CopyErrors will copy the whole internal structure and give it to you. It's an array of XERR_Error objects together with a size variable that tells you how many XERR_Error objects you should be looking at. XERR_FreeErrors will free the memory allocated by XERR_CopyError in the process of copying the internal error structure. XERR_ClearInternalSequence will clear XError's internal sequence so that a new one can be built from scratch. This is useful if you managed to handle the error, in which case you'd like XError to stop appending onto the old sequence and start a new one. XFlow ===== XFlow.h is an accompaning header file which makes some one liners clear and easy to read. For example, if you want to break if something is false: while () { BreakIf0(); } It's purely a header file full of macros: DoIf(cond, effect) DoLt0(expr, effect) DoIf0(expr, effect) ReturnIf(cond, val) ReturnLt0(expr, val) ReturnIf0(expr, val) ReturnMeLt0(expr) GotoIf(cond, label) GotoLt0(expr, label) GotoIf0(expr, label) BreakIf(cond) BreakLt0(expr) BreakIf0(expr) ContinueIf(cond) ContinueLt0(expr) ContinueIf0(expr) The Do* macros exist only to facilitate the implementation of the other ones. I don't personally encourage the use of DoIf even though I see nothing wrong with it. The ReturnMeLt0 will return the expression if it's less than 0. Local Variables =============== As seen previously, some of the macros make use of local variables to avoid double evaluation. Whenever those are used, they're prefixed with X_ERROR_ or X_FLOW_, so you should avoid those names in your code. Thread Safe and Reentrancy ========================== It's not thread safe. There is also no effort to make the code reentrant.