Section 54: On-line and off-line printing
Messages that are sent to a user’s terminal and to the transcript-log file are produced by several ‘print’ procedures. These procedures will direct their output to a variety of places, based on the setting of the global variable selector, which has the following possible values:
- TERM_AND_LOG, the normal setting, prints on the terminal and on the transcript file.
- LOG_ONLY, prints only on the transcript file.
- TERM_ONLY, prints only on the terminal.
- NO_PRINT, doesn’t print at all. This is used only in rare cases before the transcript file is open.
- PSEUDO, puts output into a cyclic buffer that is used by the show_context routine; when we get to that routine we shall discuss the reasoning behind this curious mode.
- NEW_STRING, appends the output to the current string in the string pool.
- 0 to 15, prints on one of the sixteen files for
\write
output.
The symbolic names ‘TERM_AND_LOG’, etc., have been assigned numeric codes that satisfy the convenient relations NO_PRINT + 1 = TERM_ONLY, NO_PRINT + 2 = LOG_ONLY, TERM_ONLY + 2 = LOG_ONLY + 1 = TERM_AND_LOG.
Three additional global variables, tally and term_offset and file_offset, record the number of characters that have been printed since they were most recently cleared to zero. We use tally to record the length of (possibly very long) stretches of printing; term_offset and file_offset, on the other hand, keep track of how many characters have appeared so far on the current line that has been output to the terminal or to the transcript file, respectively.
#define NO_PRINT 16 // |selector| setting that makes data disappear
#define TERM_ONLY 17 // printing is destined for the terminal only
#define LOG_ONLY 18 // printing is destined for the transcript file only
#define TERM_AND_LOG 19 // normal |selector| setting
#define PSEUDO 20 // special |selector| setting for |show_context|
#define NEW_STRING 21 // printing is deflected to the string pool
#define MAX_SELECTOR 21 // highest selector setting
⟨ Global variables 13 ⟩+≡
alpha_file log_file; // transcript of TeX session
int selector; // where to print a message
unsigned char dig[23]; // digits in a number being output
int tally; // the number of characters recently printed
int term_offset; // the number of characters on the current terminal line
int file_offset; // the number of characters on the current file line
ASCII_code trick_buf[ERROR_LINE + 1]; // circular buffer for pseudoprinting
int trick_count; // threshold for pseudoprinting, explained later
int first_count; // another variable for pseudoprinting
Section 55
⟨ Initialize the output routines 55 ⟩≡
selector = TERM_ONLY;
tally = 0;
term_offset = 0;
file_offset = 0;
Section 56
Macro abbreviations for output to the terminal and to the log file are defined here for convenience. Some systems need special conventions for terminal output, and it is possible to adhere to those conventions by changing wterm, wterm_ln, and wterm_cr in this section.
#define wterm(X, ...) printf(X, ##__VA_ARGS__)
#define wterm_char(X) printf("%c", (X))
#define wterm_ln(X, ...) printf(X"\n", ##__VA_ARGS__)
#define wterm_cr printf("\n")
#define wlog(X, ...) fprintf(log_file, X, ##__VA_ARGS__)
#define wlog_char(X) fprintf(log_file, "%c", (X))
#define wlog_ln(X, ...) fprintf(log_file, X"\n", ##__VA_ARGS__)
#define wlog_cr fprintf(log_file, "\n")
#define write_ln(F, X, ...) fprintf((F), X"\n", ##__VA_ARGS__)
#define write_char(F, X) fprintf((F), "%c", (X))
#define write_byte(F, X) fputc((X), (F))
Section 57
To end a line of text output, we call print_ln.
// << Start file |basic_printing.c|, 1382 >>
// prints an end-of-line
void print_ln() {
switch(selector) {
case TERM_AND_LOG:
wterm_cr;
wlog_cr;
term_offset = 0;
file_offset = 0;
break;
case LOG_ONLY:
wlog_cr;
file_offset = 0;
break;
case TERM_ONLY:
wterm_cr;
term_offset = 0;
break;
case NO_PRINT:
case PSEUDO:
case NEW_STRING:
do_nothing;
break;
default:
write_ln(write_file[selector], "");
}
// |tally| is not affected
}
Section 58
The print_char procedure sends one character to the desired destination, using the XCHR array to map it into an external character compatible with input_ln. All printing comes through print_ln or print_char.
// prints a single character
void print_char(ASCII_code s) {
// Section 244 (first condition)
if (s == new_line_char && selector < PSEUDO) {
print_ln();
return;
}
switch(selector) {
case TERM_AND_LOG:
wterm_char(XCHR[s]);
incr(term_offset);
if (term_offset == MAX_PRINT_LINE) {
wterm_cr;
term_offset = 0;
}
wlog_char(XCHR[s]);
incr(file_offset);
if (file_offset == MAX_PRINT_LINE) {
wlog_cr;
file_offset = 0;
}
break;
case LOG_ONLY:
wlog_char(XCHR[s]);
incr(file_offset);
if (file_offset == MAX_PRINT_LINE) {
print_ln();
}
break;
case TERM_ONLY:
wterm_char(XCHR[s]);
incr(term_offset);
if (term_offset == MAX_PRINT_LINE) {
print_ln();
}
break;
case NO_PRINT:
do_nothing;
break;
case PSEUDO:
if (tally < trick_count) {
trick_buf[tally % ERROR_LINE] = s;
}
break;
case NEW_STRING:
// we drop characters if the string space is full
if (pool_ptr < POOL_SIZE) {
append_char(s);
}
break;
default:
write_char(write_file[selector], XCHR[s]);
}
incr(tally);
}
Section 59
An entire string is output by calling print.
Note that if we are outputting the single standard ASCII character c
, we could call print(‘c’), since 'c'
= 99 is the number of a single-character string, as explained above.
But print_char(‘c’) is quicker, so goes directly to the print_char routine when it knows that this is safe.
(The present implementation assumes that it is always safe to print a visible ASCII character.)
The original print procedure has been renamed print_strnumber. The print function is used to print to terminal or files (depending of the
selector
) only strings that are hardcoded in the source. Those have the null terminator, so it is safe to use.
void print(char *s) {
int l = strlen(s);
int i;
for (i = 0; i < l; i++) {
print_char(s[i]);
}
}
// prints string s
void print_strnumber(int s) {
pool_pointer j; // current character code position
int nl; // new-line character to restore
if (s >= str_ptr) {
print("???"); // This can't happen.
}
else if (s < 256) {
if (selector > PSEUDO) {
print_char(s);
return;
// internal strings are not expanded
}
// Section 244 (first condition)
if (s == new_line_char && selector < PSEUDO) {
print_ln();
return;
}
nl = new_line_char;
new_line_char = -1; // temporary disable new-line character
j = str_start[s];
while (j < str_start[s + 1]) {
print_char(str_pool[j]);
incr(j);
}
new_line_char = nl;
return;
}
j = str_start[s];
while (j < str_start[s +1]) {
print_char(str_pool[j]);
incr(j);
}
}
Section 60
Control sequence names, file names, and strings constructed with \string
might contain ASCII_code values that can’t be printed using print_char.
Therefore we use slow_print for them:
// prints string s
void slow_print(int s) {
pool_pointer j; // current character code position
if (s >= str_ptr || s < 256) {
print_strnumber(s);
}
else {
j = str_start[s];
while (j < str_start[s + 1]) {
print_strnumber(str_pool[j]);
incr(j);
}
}
}
Section 61
Here is the very first thing that prints: a headline that identifies the version number and format package. The term_offset variable is temporarily incorrect, but the discrepancy is not serious since we assume that this part of the program is system dependent.
⟨ Initialize the output routines 55 ⟩+≡
wterm(BANNER);
if (format_ident == 0) {
wterm_ln(" (no format preloaded)");
}
else {
slow_print(format_ident);
print_ln();
}
update_terminal;
Section 62
The procedure print_nl is like print, but it makes sure that the string appears at the beginning of a new line.
Same as print, there are two versions.
void print_nl(char *s) {
if ((term_offset > 0 && odd(selector))
|| (file_offset > 0 && selector >= LOG_ONLY))
{
print_ln();
}
print(s);
}
// prints string |s| at beginning of line
void print_nl_strnumber(str_number s) {
if ((term_offset > 0 && odd(selector))
|| (file_offset > 0 && selector >= LOG_ONLY))
{
print_ln();
}
print_strnumber(s);
}
Section 63
The procedure print_esc prints a string that is preceded by the user’s escape character (which is usually a backslash).
Again, two versions are provided.
void print_esc(char *s) {
int c;
// << Set variable |c| to the current escape character, 243 >>
if (c >= 0 && c < 256) {
print_strnumber(c);
}
print(s);
}
// prints escape character, then |s|
void print_esc_strnumber(str_number s) {
int c; // the escape character code
// << Set variable |c| to the current escape character, 243 >>
if (c >= 0 && c < 256) {
print_strnumber(c);
}
slow_print(s);
}
Section 64
An array of digits in the range 0 .. 15 is printed by print_the_digs.
// prints |dig[k - 1]| ... |dig[0]|
void print_the_digs(eight_bits k) {
while (k > 0) {
decr(k);
if (dig[k] < 10) {
print_char('0' + dig[k]);
}
else {
print_char('A' - 10 + dig[k]);
}
}
}
Section 65
The following procedure, which prints out the decimal representation of a given integer n, has been written carefully so that it works properly if n = 0 or if (−n) would cause overflow. It does not apply mod or div to negative arguments, since such operations are not implemented consistently by all Pascal compilers.
// prints an integer in decimal form
void print_int(int n) {
int k = 0; // index to current digit; we assume that |n| < 10^{23}
int m; // used to negate |n| in possibly dangerous cases
if (n < 0) {
print_char('-');
if (n > -100000000) {
negate(n);
}
else {
m = -1 - n;
n = m / 10;
m = (m % 10) + 1;
k = 1;
if (m < 10) {
dig[0] = m;
}
else {
dig[0] = 0;
incr(n);
}
}
}
do {
dig[k] = n % 10;
n /= 10;
incr(k);
} while (n != 0);
print_the_digs(k);
}
Section 66
Here is a trivial procedure to print two digits; it is usually called with a parameter in the range 0 n 99.
// prints two least significant digits
void print_two(int n) {
n = abs(n) % 100;
print_char('0' + (n / 10));
print_char('0' + (n % 10));
}
Section 67
Hexadecimal printing of nonnegative integers is accomplished by print_hex.
// prints a positive integer in hexadecimal form
void print_hex(int n) {
int k = 0; // index to current digit; we assume that 0 <= n < 16^{22}
print_char('"');
do {
dig[k] = n % 16;
n /= 16;
incr(k);
} while (n != 0);
print_the_digs(k);
}
Section 68
Old versions of needed a procedure called print_ASCII whose function is now subsumed by print. We retain the old name here as a possible aid to future software archæologists.
Not kept, but the code was:
#define print_ASCII print
Section 69
Roman numerals are produced by the print_roman_int routine.
Readers who like puzzles might enjoy trying to figure out how this tricky code works;
therefore no explanation will be given.
Notice that 1990 yields mcmxc
, not mxm
.
void print_roman_int(int n) {
char *s = "m2d5c2l5x2v5i";
int j = 0, k; // mysterious indices into |s|
int u, v = 1000; // mysterious numbers
while(true) {
while (n >= v) {
print_char(s[j]);
n -= v;
}
if (n <= 0) {
break; // nonpositive input produces no output
}
k = j + 2;
u = v / (s[k - 1] - '0');
if (s[k - 1] == '2') {
k += 2;
u /= (s[k - 1] - '0');
}
if (n + u >= v) {
print_char(s[k]);
n += u;
}
else {
j += 2;
v /= (s[j - 1] - '0');
}
}
}
Section 70
The print subroutine will not print a string that is still being created. The following procedure will.
// prints a yet-unmade string
void print_current_string() {
pool_pointer j; // points to current character code
j = str_start[str_ptr];
while (j < pool_ptr) {
print_char(str_pool[j]);
incr(j);
}
}
Section 71
Here is a procedure that asks the user to type a line of input, assuming that the selector setting is either TERM_ONLY or TERM_AND_LOG. The input is placed into locations first through last − 1 of the buffer array, and echoed on the transcript file if appropriate.
// prints a string and gets a line of input
#define prompt_input(X) print((X)); term_input()
// << Start file |terminal.c|, 1382 >>
// gets a line from the terminal
void term_input() {
int k; // index into |buffer|
update_terminal; // now the user sees the prompt for sure
if (!input_ln(stdin)) {
fatal_error("End of file on the terminal");
}
term_offset = 0; // the user's line ended with <return>
decr(selector); // prepare to echo the input
if (last != first) {
for(k = first; k < last; k++) {
print_strnumber(buffer[k]);
}
}
print_ln();
incr(selector); // restore previous status
}