/* errmgr.cpp - error manager */
/* has been trimmed to fit; see the monthly code */
/* disk for the complete source. */
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "complain.hpp"
#include "utils.hpp"
#include "c_failur.hpp"
#include "errmgr.hpp"
/* an element in the list of complaint dictionaries: */
class error_dict_list {
public:
error_dict_list(error_dict_list *curr,
complaint_dict *new_dict);
error_dict_list *prev;
complaint_dict *dict;
};
error_mgr err_mgr;
/* is_set_up is set when err_mgr is initialized. no */
/* other error manager can be active. */
int error_mgr::is_set_up = 0);
/* asserts_off is 0 when assertions are to be checked. */
#ifndef NDEBUG
int error_mgr::asserts_off = 0;
#else
int error_mgr::asserts_off = 1;
#endif
failure_handler *error_mgr::curr_handler = 0,
*error_mgr::default_handler = 0;
error_dict_list *error_mgr::error_dicts = 0;
ptr_stack *error_mgr::handler_stack = 0;
static char line[512]; /* should be managed buffer */
static int proper_format(const char *pgm_text,
const char *user_text);
static char format_specifier(const char **fmt);
error_mgr::error_mgr(void) /* constructor */
{
setup();
}
error_mgr::~error_mgr(void) /* destructor */
{
delete default_handler;
delete handler_stack;
}
void error_mgr::setup(void)
{
/* create default handler and empty dictionary */
/* list, allocate stack for failure_handlers, */
/* make sure no other error_mgr is present. */
if (this != &err_mgr) /* just in case... */
err_mgr.fail("Duplicate error_mgr defined.\n");
if (is_set_up)
return;
is_set_up = 1;
curr_handler = default_handler =
new failure_handler;
handler_stack = new ptr_stack;
}
int error mgr::define_dictionary(const char *filename)
{
/* add a new dictionary to the list. it's */
/* ignored if any errors are found. */
counting_failure_handler *our_handler;
complaint_dict *new_dict;
setup();
/* we count errors in the file with our_handler. */
our_handler = new counting_failure_handler;
new_dict = new complaint_dict(filename);
if (our_handler->errors_logged() ||
our_handler->warns_logged()) {
delete new_dict;
warn("$bad_error_dict: dictionary file \"%s\" "
"has errors - will not be used\n",
filename);
delete our_handler;
return 0;
}
error_dicts = new error_dict_list(
error_dicts,new_dict);
delete our_handler;
return 1;
}
failure_handler *error_mgr::define_handler(
failure_handler *new_handler)
{
/* install a new error handler, pushing the */
/* previous handler onto a stack. */
failure_handler *old_handler;
setup();
handler_stack->push(curr_handler);
old_handler = curr_handler;
if (new_handler == 0) /* bomb-proof */
curr_handler = default_handler;
else
curr_handler = new_handler;
return old_handler;
}
failure_handler *error_mgr::restore_handler(void)
{
/* return to the previous error handler. */
failure_handler *old_handler;
setup();
old_handler = curr_handler;
curr_handler = (failure_handler *)
(handler_stack->pop());
if (curr_handler == 0)
curr_handler = default_handler;
return old_handler;
}
const char *error_mgr::message(
const char *fmt,char *msg_line,
int linelen)
{
/* search for the replacement text, writing */
/* it into msg_line if found. return a pointer */
/* to the start of the message, ready to print. */
const char *new_fmt,*key,*keystart;
char *key_alloc,*buf;
int keylen;
/* this routine calls setup() for fail(), */
/* vfail(), etc. */
setup();
ASSERT(fmt != msg_line);
msg_line[0] = '\0';
if (*fmt ! = '$') { /* not keyed? */
strncpy(msg_line,fmt,linelen);
msg_line[linelen - 1] = '\0';
return msg_line;
}
/* extract the key. */
key = ++fmt; /* skip '$' */
key += skipblanks(key);
keylen = skip_ident(key); /* fetch key name */
new_fmt = key + keylen; /* skip key name */
new_fmt += skipblanks(key); /* skip to ':' */
/* error text here is not replaceable - */
/* recursive calls would be dangerous. */
if (keylen == 0 || ll *new_fmt != ':') {
sprintf(msg_line,"Keyed error message format"
"string is malformed\n%s",fmt);
}
else {
key_alloc = newstring(key,keylen);
++new_fmt; /* skip ':' */
new_fmt += skipblanks (new_fmt);
if (find_replacement(key_alloc,msg_line,
linelen)) {
if (!proper_format(new_fmt,msg_line)) {
/* invalid; dictionary text left at */
/* front of resulting error message. */
buf = msg_line + strlen(msg_line);
sprintf(buf,"Replacement text for "
"keyed error message is "
"malformed\n%s", fmt);
}
}
else { /* use program version */
strncpy(msg_line,new_fmt,linelen);
msg_line[linelen - 1] = '\0';
}
free(key_alloc);
}
return msg_line;
}
int error_mgr::find_replacement(
const char *key,char *msg_line,
int linelen)
{
/* search for message replacement. returns 1 */
/* if found, 0 if not. */
error_dict_list *curr_dict;
for (curr_dict = error_dicts; curr_dict != 0;
curr_dict = curr_dict->prev)
if (curr_dict->dict->key_defined(key))
break;
return curr_dict != 0 &&
curr_dict->dict->complaint_text(
key,msg_line,linelen);
}
static int proper_format(const char *pgm_text,
const char *user_text)
{
/* return 1 if the printf() format specifiers */
/* in user_text match the ones in pgm_text. */
char pgm_fmt,user_fmt;
do {
pgm_fmt = format_specifier(&pgm_text);
user_fmt = format_specifier(&user_text);
if (pgm_fmt!= user_fmt)
return 0;
} while (*pgm_text || *user_text);
return 1;
}
static char format_specifier(const char **fmt)
{
/* skip to the next '%' specifier (if any) */
/* and return a unique letter indicating its */
/* type. advances *fmt past the specifier. */
const char *s : *fmt; /* *s vs. **fmt */
char c;
for (;;) { /* skip to '%' */
while (*s && *s != '%')
++s;
if (*s == '\0')
break;
++s; /* skip '%' */
if (*s == '%') /* "%%" prints '%' */
++s; /* not specifier */
else
break;
}
if (*s == '\0') {
*fmt = s; /* update caller var */
return '\0';
}
/* skip to the specifier letter. */
while (*s && !isspace(*s) && !isalpha(*s))
++s;
if (!isalpha(*s)) {
*fmt = s; /* update caller var */
return '\0'; /* bad specifier */
}
/* any 'l' is assumed to be long. */
if (*s == 'l' && isalpha(*(s + 1))) {
*fmt = s + 2; /* skip l, specifier */
return '1';
}
/* map the specifier into a canonical value. */
c = tolower(*s);
*fmt = s + 1; /* update caller var */
switch(c) {
case 'e': case 'f': case 'g':
return 'e'; /* floating point */
case 'o': case 'u': case 'x':
case 'd': case 'i': case 'b':
return 'd'; /* int */
default: /* all others map */
return c; /* to themselves */
} /* end of switch(c) */
/* NOTREACHED */
}
void error_mgr::fail(const char *fmt,...)
{
/* print the message and exit. */
va_list ap;
va_start(ap,fmt);
vfail(fmt,ap); /* won't return */
/* NOTREACHED */
va_end(ap);
}
void error_mgr::vfail(const char *fmt,va_list ap)
{
/* fail() with a va_list already built. if */
/* the handler doesn't exit we force it. */
fmt = message(fmt,line,sizeof(line));
curr_handler->fail(fmt,ap);
exit(EXIT_FAILURE); /* just in case! */
}
void error_mgr::error(const char *fmt,...)
{
/* print the message and ask for permission to */
/* continue. */
va_list ap;
va_start(ap,fmt);
verror(fmt,ap);
va_end(ap);
}
void error_mgr::verror(const char *fmt,va_list ap)
{
/* error() with a va_list already built. */
/* we replace the message if possible. */
fmt = message(fmt,line,sizeof(line));
curr_handler->error(fmt,ap);
}
int error_mgr::set_assert_flag(int asserts_on)
{
/* enable or disable assertions at run time. */
int retval = asserts_off;
/* set to opposite state because we want to */
/* short-circuit assertion evaluation. */
asserts_off = !asserts_on;
return !retval;
}
int error_mgr::assert_failed(
const char *exp,const char *fname,
unsigned linenum)
{
/* an assertion has failed. */
error("Assertion failed - file %s, line %u:\n%s\n",
fname,linenum,exp);
return 1; /* needed for macro */
}
// End of File