Richard Heathfield, October 2002
An error message code generator written
in ISO C.
What's the problem?
What's the solution?
How do I use it?
Example
Download
Support (none)
Acknowledgements
What's the problem?
You're a C programmer. You write C programs. Big ones. Complicated ones.
Because you know what you're doing, you understand the importance of keeping
track of errors. You use numeric codes to identify particular kinds of error
(or other program condition).
You want to associate each error code with a string describing the error.
Fine, no problem. An enumerated type for the numbers, and an array of strings
for the error descriptions. Easy.
But keeping the enums in line with the strings can be a pain when it comes
to adding new errors to the list. Most programmers seem to solve this problem
either by tacking all new codes on the end, which wreaks havoc on any logical
order that might have been put into the original list, or by "banding", which
can be a real hassle to remember.
Surely there's a better way.
What's the solution?
It's quite simple - keep all the codes and strings in the same place.
And this is how emgen works. Its input is a text file, which comprises
one error code and string per line. The error code is not a number, but a
C identifier. emgen reads this file and produces two output files - a header
file containing an enumerated type with all the error codes in, and a C
source file which contains a function suitable for retrieving the text associated
with the error.
How do I use it?
emgen <errortext> <cfile> <hfile> <functionname>
<maxlenidentifier>
<errortext> is the name of the error file. I'll let you
guess what <cfile> and <hfile> are. <functionname>
is the name of the function which will translate error codes into strings.
This function requires a pointer to sufficient memory to store the string,
so it would be handy for the user to know how much memory is required. That's
where <maxlenidentifier> comes in - it is used for generating
a #define in the header file that gives an amount of memory guaranteed
to be enough to store any one of the error strings.
Example
Consider this error text file:
# Leading comments are preserved (but translated into C comments,
so don't use * and / in your comment!)
# Comments are indicated by a leading #
# Blank lines are elided.
MYERR_SUCCESS No error.
MYERR_LOWMEM The application is running
low on memory.
MYERR_FOOFUR There is too much fur
on the foo.
# Once we've started on the error codes, further comments are legal but
ignored.
MYERR_NULLPTR The application passed a
null pointer to the zog function.
That'll do. Save that as test.emgen and we can carry on. Here's
a sample command line.
emgen test.emgen testemgen.c testemgen.h EmgenTest MAX_ERR_LEN
Here's the resulting header, testemgen.h:
/* Leading comments are preserved (but translated into C comments,
so don't use * and / in your comment!) */
/* Comments are indicated by a leading # */
/* Blank lines are elided. */
/* testemgen.h is a machine generated file - do not alter */
#ifndef TESTEMGEN_H_
#define TESTEMGEN_H_ 1
enum TESTEMGEN_H_MESSAGES
{
/*! No error. */
MYERR_SUCCESS,
/*! The application is running low on memory. */
MYERR_LOWMEM,
/*! There is too much fur on the foo. */
MYERR_FOOFUR,
/*! The application passed a null pointer to the zog function. */
MYERR_NULLPTR
};
#define MAX_ERR_LEN 64
const char *EmgenTest(int errornumber);
#endif
/* end of machine-generated file testemgen.h */
NOTE to doxygen fans: yes, doxygen comments have been added into the enum
list.
Here's the C file:
/* Leading comments are preserved (but translated into C comments,
so don't use * and / in your comment!) */
/* Comments are indicated by a leading # */
/* Blank lines are elided. */
/* testemgen.c is a machine generated file - do not alter */
#include <stdio.h>
#include <string.h>
#include "testemgen.h"
const char *EmgenTest(int errornumber)
{
static const char *emsg[] =
{
"No error.",
"The application is running low on memory.",
"There is too much fur on the foo.",
"The application passed a null pointer to the zog function."
};
size_t maxmsgno = sizeof emsg / sizeof emsg[0];
const char *errormsg = "Unexpected error.";
if(errornumber >= 0 && errornumber < (int)maxmsgno)
{
errormsg = emsg[errornumber];
}
return errormsg;
}
/* end of generated file testemgen.c */
Download
The files you need are emgen.c, fgetline.c, and
fgetline.h, which you can find in this
tarball.
None. Sorry and all that, but I have quite enough support issues right
now. Take it or leave it, love it or loathe it - it's up to you. Having said
that, I use it myself, so if I find any bugs I'll try to remember to update
the tarball.
Acknowledgements
Lawrence Kirby is the original author of the "round up to next power of two"
code.
Rouben Rostamian suggested returning a const char * rather than accepting
a buffer - a suggestion I was delighted to take up. He also pointed out
that the fgetline() function wasn't very friendly to text files
not ending in a newline character. Whilst I have no sympathy for people
who create such files :-) I have nevertheless fixed the code so that
the last line will be handled properly whether or not it ends in a newline.
Thomas Stegen pointed out a couple of coding style inconsistencies which
I have now fixed.
Arthur O'Dwyer noticed that the generated code exhibits undefined behaviour
if the header name starts with an e! This is now fixed.