Section 72: Reporting errors
When something anomalous is detected, typically does something like this:
print_err("Something anomalous has been detected");
help3("This is the first line of my offer to help.")
("This is the second line. I'm trying to")
("explain the best way for you to proceed.");
error;
A two-line help message would be given using help2, etc.; these informal helps should use simple vocabulary that complements the words used in the official error message that was printed. (Outside the U.S.A., the help messages should preferably be translated into the local vernacular. Each line of help is at most 60 characters long, in the present implementation, so that MAX_PRINT_LINE will not be exceeded.)
The print_err procedure supplies a ‘!
’ before the official message, and makes sure that the terminal is awake if a stop is going to occur.
The error procedure supplies a ‘.
’ after the official message, then it shows the location of the error; and if interaction = ERROR_STOP_MODE, it also enters into a dialog with the user, during which time the help message may be printed.
Section 73
The global variable interaction has four settings, representing increasing amounts of user interaction:
#define BATCH_MODE 0 // omits all stops and omits terminal output
#define NONSTOP_MODE 1 // omits all stops
#define SCROLL_MODE 2 // omits error stops
#define ERROR_STOP_MODE 3 // stops at every opportunity to interact
// << Start file |texerror.h|, 1381 >>
#define print_err(X) print_nl("! "); print((X))
⟨ Global variables 13 ⟩+≡
int interaction; // current level of interaction
Section 74
⟨ Set initial values of key variables 21 ⟩+≡
interaction = ERROR_STOP_MODE;
Section 75
is careful not to call error when the print selector setting might be unusual. The only possible values of selector at the time of error messages are
- NO_PRINT (when interaction = BATCH_MODE and log_file not yet open);
- TERM_ONLY (when interaction > BATCH_MODE and log_file not yet open);
- LOG_ONLY (when interaction = BATCH_MODE and log_file is open);
- TERM_AND_LOG (when interaction > BATCH_MODE and log_file is open).
⟨ Initialize the print selector based on interaction 75 ⟩≡
if (interaction == BATCH_MODE) {
selector = NO_PRINT;
}
else {
selector = TERM_ONLY;
}
Section 76
A global variable deletions_allowed is set false if the get_next routine is active when error is called; this ensures that get_next and related routines like get_token will never be called recursively. A similar interlock is provided by set_box_allowed.
The global variable history records the worst level of error that has been detected. It has four possible values: SPOTLESS, WARNING_ISSUED, ERROR_MESSAGE_ISSUED, and FATAL_ERROR_STOP.
Another global variable, error_count, is increased by one when an error occurs without an interactive dialog, and it is reset to zero at the end of every paragraph. If error_count reaches 100, decides that there is no point in continuing further.
#define SPOTLESS 0 // |history| value when nothing has been amiss yet
#define WARNING_ISSUED 1 // |history| value when |begin_diagnostic| has been called
#define ERROR_MESSAGE_ISSUED 2 // |history| value when |error| has been called
#define FATAL_ERROR_STOP 3 // |history| value when termination was premature
⟨ Global variables 13 ⟩+≡
bool deletions_allowed; // is it safe for |error| to call |get_token|?
bool set_box_allowed; // is it safe to do a \setbox assignment?
int history; // has the source input been clean so far?
int error_count; // the number of scrolled errors since the last paragraph ended
Section 77
The value of history is initially FATAL_ERROR_STOP, but it will be changed to SPOTLESS if survives the initialization process.
⟨ Set initial values of key variables 21 ⟩+≡
deletions_allowed = true;
set_box_allowed = true;
error_count = 0;
// history is initialized elsewhere
Section 78
Since errors can be detected almost anywhere in , we want to declare the error procedures near the beginning of the program. But the error procedures in turn use some other procedures, which need to be declared forward before we get to error itself.
It is possible for error to be called recursively if some error arises when get_token is being used to delete a token, and/or if some fatal error occurs while is trying to fix a non-fatal one. But such recursion is never more than two levels deep.
No need for forward declaration; they are in header files.
Section 79
Individual lines of help are recorded in the array help_line, which contains entries in positions 0 .. (help_ptr − 1). They should be printed in reverse order, i.e., with help_line[0] appearing last.
#define hlp1(X) help_line[0] = (X)
#define hlp2(X) help_line[1] = (X); hlp1
#define hlp3(X) help_line[2] = (X); hlp2
#define hlp4(X) help_line[3] = (X); hlp3
#define hlp5(X) help_line[4] = (X); hlp4
#define hlp6(X) help_line[5] = (X); hlp5
#define help0 help_ptr = 0 // sometimes there might be no help
#define help1 help_ptr = 1; hlp1 // use this with one help line
#define help2 help_ptr = 2; hlp2 // use this with two help lines
#define help3 help_ptr = 3; hlp3 // use this with three help lines
#define help4 help_ptr = 4; hlp4 // use this with four help lines
#define help5 help_ptr = 5; hlp5 // use this with five help lines
#define help6 help_ptr = 6; hlp6 // use this with six help lines
⟨ Global variables 13 ⟩+≡
char *help_line[6]; // helps for the next |error|
int help_ptr; // the number of help lines present
bool use_err_help; // should the |err_help| list be shown?
Section 80
⟨ Set initial values of key variables 21 ⟩+≡
help_ptr = 0;
use_err_help = false;
Section 81
The jump_out procedure just cuts across all active procedure levels and goes to end_of_TEX. This is the only nontrivial goto statement in the whole program. It is used when there is no recovery from a particular error.
Some Pascal compilers do not implement non-local goto statements. In such cases the body of jump_out should simply be ‘close_files_and_terminate;’ followed by a call on some system procedure that quietly terminates the program.
// << Start file |error.c|, 1382 >>
void jump_out() {
close_files_and_terminate();
exit(0);
}
Section 82
Here now is the general error routine.
void error() {
ASCII_code c; // what the user types
int s1, s2, s3, s4; // used to save global variables when deleting tokens
if (history < ERROR_MESSAGE_ISSUED) {
history = ERROR_MESSAGE_ISSUED;
}
print_char('.');
show_context();
if (interaction == ERROR_STOP_MODE) {
// << Get user's advice and |return|, 83 >>
}
incr(error_count);
if (error_count == 100) {
print_nl("(That makes 100 errors; please try again.)");
history = FATAL_ERROR_STOP;
jump_out();
}
// << Put help message on the transcript file, 90 >>
}
Section 83
⟨ Get user’s advice and return 83 ⟩≡
while(true) {
if (interaction != ERROR_STOP_MODE) {
return;
}
clear_for_error_prompt();
prompt_input("? ");
if (last == first) {
return;
}
c = buffer[first];
if (c >= 'a') {
c += 'A' - 'a'; // onvert to uppercase.
}
// << Interpret code |c| and |return| if done, 84 >>
}
Section 84
It is desirable to provide an ‘E
’ option here that gives the user an easy way to return from to the system editor, with the offending line ready to be edited.
But such an extension requires some system wizardry, so the present implementation simply types out the name of the file that should be edited and the relevant line number.
There is a secret ‘D
’ option available when the debugging routines haven’t been commented~out.
No ‘E’ option: input edition not supported.
⟨ Interpret code c and return if done 84 ⟩≡
switch(c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '7':
case '8':
case '9':
if (deletions_allowed) {
// << Delete |c - '0'| tokens and |goto continue|, 88 >>
}
break;
#ifdef DEBUG
case 'D':
debug_help();
continue;
#endif
case 'H':
// << Print the help information and |goto continue|, 89 >>
break;
case 'I':
// << Introduce new material from the terminal and |return|, 87 >>
break;
case 'Q':
case 'R':
case 'S':
// << Change the interaction level and |return|, 86 >>
break;
case 'X':
interaction = SCROLL_MODE;
jump_out();
default:
do_nothing;
}
// << Print the menu of available options, 85 >>
Section 85
⟨ Print the menu of available options 85 ⟩≡
print("Type <return> to proceed, S to scroll future error messages,");
print_nl("R to run without stopping, Q to run quietly,");
print_nl("I to insert something, ");
if (deletions_allowed) {
print_nl("1 or .... or 9 to ignore the next 1 to 9 tokens of input,");
}
print_nl("H for help, X to quit.");
Section 86
Here the author of apologizes for making use of the numerical relation between 'Q'
, 'R'
, 'S'
, and the desired interaction settings BATCH_MODE, NONSTOP_MODE, SCROLL_MODE.
⟨ Change the interaction level and return 86 ⟩≡
error_count = 0;
interaction = BATCH_MODE + c - 'Q';
print("OK, entering ");
switch(c) {
case 'Q':
print_esc("batchmode");
decr(selector);
break;
case 'R':
print_esc("nonstopmode");
break;
case 'S':
print_esc("scrollmode");
break;
} // there are no other cases
print("...");
print_ln();
update_terminal;
return;
Section 87
When the following code is executed, buffer[(first + 1) .. (last − 1)] may contain the material inserted by the user; otherwise another prompt will be given. In order to understand this part of the program fully, you need to be familiar with ’s input stacks.
⟨ Introduce new material from the terminal and return 87 ⟩≡
begin_file_reading(); // enter a new syntactic level for terminal input
// now |state = MID_LINE|, so an initial blank space will count as a blank
if (last > first + 1) {
loc = first + 1;
buffer[first] = ' ';
}
else {
prompt_input("insert>");
loc = first;
}
first = last;
cur_input.limit_field = last - 1; // no |end_line_char| ends this line
return;
Section 88
We allow deletion of up to 99 tokens at a time.
⟨ Delete c - ‘0’ tokens and goto continue 88 ⟩≡
s1 = cur_tok;
s2 = cur_cmd;
s3 = cur_chr;
s4 = align_state;
align_state = 1000000;
ok_to_interrupt = false;
if (last > first + 1
&& buffer[first + 1] >= '0'
&& buffer[first + 1] <= '9')
{
c = c * 10 + buffer[first + 1] - '0' * 11;
}
else {
c -= '0';
}
while (c > 0) {
get_token(); // one-level recursive call of |error| is possible
decr(c);
}
cur_tok = s1;
cur_cmd = s2;
cur_chr = s3;
align_state = s4;
ok_to_interrupt = true;
help2("I have just deleted some text, as you asked.")
("You can now delete more, or insert, or whatever.");
show_context();
continue;
Section 89
⟨ Print the help information and goto continue 89 ⟩≡
if (use_err_help) {
give_err_help();
use_err_help = false;
}
else {
if (help_ptr == 0) {
help2("Sorry, I don't know how to help in this situation.")
("Maybe you should try asking a human?");
}
do {
decr(help_ptr);
print(help_line[help_ptr]);
print_ln();
} while (help_ptr != 0);
}
help4("Sorry, I already gave what help I could...")
("Maybe you should try asking a human?")
("An error might have occurred before I noticed any problems.")
("``If all else fails, read the instructions.''");
continue;
Section 90
⟨ Put help message on the transcript file 90 ⟩≡
if (interaction > BATCH_MODE) {
decr(selector); // avoid terminal output
}
if (use_err_help) {
print_ln();
give_err_help();
}
else {
while (help_ptr > 0) {
decr(help_ptr);
print_nl(help_line[help_ptr]);
}
}
print_ln();
if (interaction > BATCH_MODE) {
incr(selector); // re-enable terminal output
}
print_ln();
Section 91
A dozen or so error messages end with a parenthesized integer, so we save a teeny bit of program space by declaring the following procedure:
void int_error(int n) {
print(" (");
print_int(n);
print_char(')');
error();
}
Section 92
In anomalous cases, the print selector might be in an unknown state; the following subroutine is called to fix things just enough to keep running a bit longer.
void normalize_selector() {
if (log_opened) {
selector = TERM_AND_LOG;
}
else {
selector = TERM_ONLY;
}
if (job_name == 0) {
open_log_file();
}
if (interaction == BATCH_MODE) {
decr(selector);
}
}
Section 93
The following procedure prints ’s last words before dying.
void succumb() {
if (interaction == ERROR_STOP_MODE) {
interaction = SCROLL_MODE; // no more interaction
}
if (log_opened) {
error();
}
#ifdef DEBUG
if (interaction > BATCH_MODE) {
debug_help();
}
#endif
history = FATAL_ERROR_STOP;
jump_out(); // irrecoverable error
}
// prints |s| and that's it
void fatal_error(char *s) {
normalize_selector();
print_err("Emergency stop");
help1(s);
succumb();
}
Section 94
Here is the most dreaded error message.
// stop due to finiteness
void overflow(char *s, int n) {
normalize_selector();
print_err("TeX capacity exceeded, sorry [");
print(s);
print_char('=');
print_int(n);
print_char(']');
help2("If you really absolutely need more capacity,")
("you can ask a wizard to enlarge me.");
succumb();
}
Section 95
The program might sometime run completely amok, at which point there is no choice but to stop. If no previous error has been detected, that’s bad news; a message is printed that is really intended for the maintenance person instead of the user (unless the user has been particularly diabolical). The index entries for ‘this can’t happen’ may help to pinpoint the problem.
// consistency check violated; |s| tells where
void confusion(char *s) {
normalize_selector();
if (history < ERROR_MESSAGE_ISSUED) {
print_err("This can't happen (");
print(s);
print_char(')');
help1("I´m broken. Please show this to someone who can fix can fix");
}
else {
print_err("I can't go on meeting you like this");
help2("One of your faux pas seems to have wounded me deeply...")
("in fact, I'm barely conscious. Please fix it and try again.");
}
succumb();
}
Section 96
Users occasionally want to interrupt while it’s running. If the Pascal runtime system allows this, one can implement a routine that sets the global variable interrupt to some nonzero value when such an interrupt is signalled. Otherwise there is probably at least a way to make interrupt nonzero using the Pascal debugger.
#define check_interrupt \
do { \
if (interrupt != 0) { \
pause_for_instructions(); \
} \
} while (0)
⟨ Global variables 13 ⟩+≡
int interrupt; // should TeX pause for instructions?
bool ok_to_interrupt; // should interrupts be observed?
Section 97
⟨ Set initial values of key variables 21 ⟩+≡
interrupt = 0;
ok_to_interrupt = true;
Section 98
When an interrupt has been detected, the program goes into its highest interaction level and lets the user have nearly the full flexibility of the error routine. checks for interrupts only at times when it is safe to do this.
void pause_for_instructions() {
if (ok_to_interrupt) {
interaction = ERROR_STOP_MODE;
if (selector == LOG_ONLY || selector == NO_PRINT) {
incr(selector);
}
print_err("Interruption");
help3("You rang?")
("Try to insert an instruction for me (e.g. `I\\showlists'),")
("unless you just want to quit by typing `X'.");
deletions_allowed = false;
error();
deletions_allowed = true;
interrupt = 0;
}
}