Skip to content

phao/XError

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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) {
    <CODE TO ISSUE AN ERROR>
    return test;
  }

This "<CODE TO ISSUE AN ERROR>" 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 "<CODE TO ISSUE AN ERROR>" 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 (<Cond>) {
    <DoSomething>
    BreakIf0(<ShouldBeFalseToBreak>);
    <DoSomethingElse>
  }

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.

About

An error reporting handling library for C.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages