Section 487: Conditional processing
We consider now the way handles various kinds of \if
commands.
#define IF_CHAR_CODE 0 // '\if'
#define IF_CAT_CODE 1 // '\ifcat'
#define IF_INT_CODE 2 // '\ifnum'
#define IF_DIM_CODE 3 // '\ifdim'
#define IF_ODD_CODE 4 // '\ifodd'
#define IF_VMODE_CODE 5 // '\ifvmode'
#define IF_HMODE_CODE 6 // '\ifhmode'
#define IF_MMODE_CODE 7 // '\ifmmode'
#define IF_INNER_CODE 8 // '\ifinner'
#define IF_VOID_CODE 9 // '\ifvoid'
#define IF_HBOX_CODE 10 // '\ifhbox'
#define IF_VBOX_CODE 11 // '\ifvbox'
#define IFX_CODE 12 // '\ifx'
#define IF_EOF_CODE 13 // '\ifeof'
#define IF_TRUE_CODE 14 // '\iftrue'
#define IF_FALSE_CODE 15 // '\iffalse'
#define IF_CASE_CODE 16 // '\ifcase'
⟨ Put each of TeX’s primitives into the hash table 226 ⟩+≡
primitive("if", IF_TEST, IF_CHAR_CODE);
primitive("ifcat", IF_TEST, IF_CAT_CODE);
primitive("ifnum", IF_TEST, IF_INT_CODE);
primitive("ifdim", IF_TEST, IF_DIM_CODE);
primitive("ifodd", IF_TEST, IF_ODD_CODE);
primitive("ifvmode", IF_TEST, IF_VMODE_CODE);
primitive("ifhmode", IF_TEST, IF_HMODE_CODE);
primitive("ifmmode", IF_TEST, IF_MMODE_CODE);
primitive("ifinner", IF_TEST, IF_INNER_CODE);
primitive("ifvoid", IF_TEST, IF_VOID_CODE);
primitive("ifhbox", IF_TEST, IF_HBOX_CODE);
primitive("ifvbox", IF_TEST, IF_VBOX_CODE);
primitive("ifx", IF_TEST, IFX_CODE);
primitive("ifeof", IF_TEST, IF_EOF_CODE);
primitive("iftrue", IF_TEST, IF_TRUE_CODE);
primitive("iffalse", IF_TEST, IF_FALSE_CODE);
primitive("ifcase", IF_TEST, IF_CASE_CODE);
Section 488
⟨ Cases of print_cmd_chr for symbolic printing of primitives 227 ⟩+≡
case IF_TEST:
switch (chr_code) {
case IF_CAT_CODE:
print_esc("ifcat");
break;
case IF_INT_CODE:
print_esc("ifnum");
break;
case IF_DIM_CODE:
print_esc("ifdim");
break;
case IF_ODD_CODE:
print_esc("ifodd");
break;
case IF_VMODE_CODE:
print_esc("ifvmode");
break;
case IF_HMODE_CODE:
print_esc("ifhmode");
break;
case IF_MMODE_CODE:
print_esc("ifmmode");
break;
case IF_INNER_CODE:
print_esc("ifinner");
break;
case IF_VOID_CODE:
print_esc("ifvoid");
break;
case IF_HBOX_CODE:
print_esc("ifhbox");
break;
case IF_VBOX_CODE:
print_esc("ifvbox");
break;
case IFX_CODE:
print_esc("ifx");
break;
case IF_EOF_CODE:
print_esc("ifeof");
break;
case IF_TRUE_CODE:
print_esc("iftrue");
break;
case IF_FALSE_CODE:
print_esc("iffalse");
break;
case IF_CASE_CODE:
print_esc("ifcase");
break;
default:
print_esc("if");
}
break;
Section 489
Conditions can be inside conditions, and this nesting has a stack that is independent of the save_stack.
Four global variables represent the top of the condition stack: cond_ptr points to pushed-down entries, if any; if_limit specifies the largest code of a FI_OR_ELSE command that is syntactically legal; cur_if is the name of the current type of conditional; and if_line is the line number at which it began.
If no conditions are currently in progress, the condition stack has the special state cond_ptr = null, if_limit = NORMAL, cur_if = 0, if_line = 0. Otherwise cond_ptr points to a two-word node; the type, subtype, and link fields of the first word contain if_limit, cur_if, and cond_ptr at the next level, and the second word contains the corresponding if_line.
#define IF_NODE_SIZE 2 // number of words in stack entry for conditionals
#define IF_CODE 1 // code for \if... being evaluated
#define FI_CODE 2 // code for \f
#define ELSE_CODE 3 // code for \else
#define OR_CODE 4 // code for \or
// << Start file |conditional.h|, 1381 >>
#define if_line_field(X) mem[(X) + 1].integer
⟨ Global variables 13 ⟩+≡
pointer cond_ptr; // top of the condition stack
int if_limit; // upper bound on |fi_or_else| codes
small_number cur_if; // type of conditional being worked on
int if_line; // line where that conditional began
Section 490
⟨ Set initial values of key variables 21 ⟩+≡
cond_ptr = null;
if_limit = NORMAL;
cur_if = 0;
if_line = 0;
Section 491
⟨ Put each of TeX’s primitives into the hash table 226 ⟩+≡
primitive("fi", FI_OR_ELSE, FI_CODE);
text(FROZEN_FI) = str_ptr - 1; // "fi"
eqtb[FROZEN_FI] = eqtb[cur_val];
primitive("or", FI_OR_ELSE, OR_CODE);
primitive("else", FI_OR_ELSE, ELSE_CODE);
Section 492
⟨ Cases of print_cmd_chr for symbolic printing of primitives 227 ⟩+≡
case FI_OR_ELSE:
if (chr_code == FI_CODE) {
print_esc("fi");
}
else if (chr_code == OR_CODE) {
print_esc("or");
}
else {
print_esc("else");
}
break;
Section 493
When we skip conditional text, we keep track of the line number where skipping began, for use in error messages.
⟨ Global variables 13 ⟩+≡
int skip_line; // skipping began here
Section 494
Here is a procedure that ignores text until coming to an \or
, \else
, or \fi
at the current level of \if ... \fi
nesting.
After it has acted, cur_chr will indicate the token that was found, but cur_tok will not be set (because this makes the procedure run faster).
// << Start file |conditional.c|, 1382 >>
void pass_text() {
int l; // level of \if ... \fi nesting
small_number save_scanner_status; // |scanner_status| upon entry
save_scanner_status = scanner_status;
scanner_status = SKIPPING;
l = 0;
skip_line = line;
while(true) {
get_next();
if (cur_cmd == FI_OR_ELSE) {
if (l == 0) {
break; // Goto done
}
if (cur_chr == FI_CODE) {
decr(l);
}
}
else if (cur_cmd == IF_TEST) {
incr(l);
}
}
// done:
scanner_status = save_scanner_status;
}
Section 495
When we begin to process a new \if
, we set if_limit ← IF_CODE;
then if \or
or \else
or \fi
occurs before the current \if
condition has been evaluated, \relax
will be inserted.
For example, a sequence of commands like ‘\ifvoid1\else...\fi
’ would otherwise require something after the ‘1
’.
⟨ Push the condition stack 495 ⟩≡
p = get_node(IF_NODE_SIZE);
link(p) = cond_ptr;
type(p) = if_limit;
subtype(p) = cur_if;
if_line_field(p) = if_line;
cond_ptr = p;
cur_if = cur_chr;
if_limit = IF_CODE;
if_line = line;
Section 496
⟨ Pop the condition stack 496 ⟩≡
p = cond_ptr;
if_line = if_line_field(p);
cur_if = subtype(p);
if_limit = type(p);
cond_ptr = link(p);
free_node(p, IF_NODE_SIZE);
Section 497
Here’s a procedure that changes the if_limit code corresponding to a given value of cond_ptr.
void change_if_limit(small_number l, pointer p) {
pointer q;
if (p == cond_ptr) {
if_limit = l; // that's the easy case
}
else {
q = cond_ptr;
while(true) {
if (q == null) {
confusion("if");
}
if (link(q) == p) {
type(q) = l;
return;
}
q = link(q);
}
}
}
Section 498
A condition is started when the expand procedure encounters an IF_TEST command; in that case expand reduces to conditional, which is a recursive procedure.
void conditional() {
bool b = false; // is the condition true?
unsigned char r; // relation to be evaluated
int m, n; // to be tested against the second operand
pointer p, q; // for traversing token lists in \\ifx} tests
small_number save_scanner_status; // |scanner_status| upon entry
pointer save_cond_ptr; // |cond_ptr| corresponding to this conditional
small_number this_if; // type of this conditional
// << Push the condition stack, 495 >>
save_cond_ptr = cond_ptr;
this_if = cur_chr;
// << Either process \ifcase or set |b| to the value of a boolean condition, 501 >>
if (tracing_commands > 1) {
// << Display the value of |b|, 502 >>
}
if (b) {
change_if_limit(ELSE_CODE, save_cond_ptr);
return; // wait for \else} or \fi
}
// << Skip to \else or \fi, then |goto common_ending|, 500 >>
common_ending:
if (cur_chr == FI_CODE) {
// << Pop the condition stack, 496 >>
}
else {
if_limit = FI_CODE; // wait for \fi
}
}
Section 499
In a construction like ‘\if\iftrue abc\else d\fi
’, the first \else
that we come to after learning that the \if
is false is not the \else
we’re looking for.
Hence the following curious logic is needed.
Section 500
⟨ Skip to \else or \fi, then goto common_ending 500 ⟩≡
while(true){
pass_text();
if (cond_ptr == save_cond_ptr) {
if (cur_chr != OR_CODE) {
goto common_ending;
}
print_err("Extra ");
print_esc("or");
help1("I'm ignoring this; it doesn't match any \\if.");
error();
}
else if (cur_chr == FI_CODE) {
// << Pop the condition stack, 496 >>
}
}
Section 501
⟨ Either process \ifcase or set b to the value of a boolean condition 501 ⟩≡
switch (this_if) {
case IF_CHAR_CODE:
case IF_CAT_CODE:
// << Test if two characters match, 506 >>
break;
case IF_INT_CODE:
case IF_DIM_CODE:
// << Test relation between integers or dimensions, 503 >>
break;
case IF_ODD_CODE:
// << Test if an integer is odd, 504 >>
break;
case IF_VMODE_CODE:
b = (abs(mode) == VMODE);
break;
case IF_HMODE_CODE:
b = (abs(mode) == HMODE);
break;
case IF_MMODE_CODE:
b = (abs(mode) == MMODE);
break;
case IF_INNER_CODE:
b = (mode < 0);
break;
case IF_VOID_CODE:
case IF_HBOX_CODE:
case IF_VBOX_CODE:
// << Test box register status, 505 >>
break;
case IFX_CODE:
// << Test if two tokens match, 507 >>
break;
case IF_EOF_CODE:
scan_four_bit_int();
b = (read_open[cur_val] == CLOSED);
break;
case IF_TRUE_CODE:
b = true;
break;
case IF_FALSE_CODE:
b = false;
break;
case IF_CASE_CODE:
// << Select the appropriate case and |return| or |goto common_ending|, 509 >>
} // there are no other cases
Section 502
⟨ Display the value of b 502 ⟩≡
begin_diagnostic();
if (b) {
print("{true}");
}
else {
print("{false}");
}
end_diagnostic(false);
Section 503
Here we use the fact that '<'
, '='
, and '>'
are consecutive ASCII codes.
⟨ Test relation between integers or dimensions 503 ⟩≡
if (this_if == IF_INT_CODE) {
scan_int();
}
else {
scan_normal_dimen;
}
n = cur_val;
// << Get the next non-blank non-call token, 406 >>
if (cur_tok >= OTHER_TOKEN + '<' && cur_tok <= OTHER_TOKEN + '>') {
r = cur_tok - OTHER_TOKEN;
}
else {
print_err("Missing = inserted for ");
print_cmd_chr(IF_TEST, this_if);
help1("I was expecting to see `<', `=', or `>'. Didn't.");
back_error();
r = '=';
}
if (this_if == IF_INT_CODE) {
scan_int();
}
else {
scan_normal_dimen;
}
switch (r) {
case '<':
b = (n < cur_val);
break;
case '=':
b = (n == cur_val);
break;
case '>':
b = (n > cur_val);
}
Section 504
⟨ Test if an integer is odd 504 ⟩≡
scan_int();
b = odd(cur_val);
Section 505
⟨ Test box register status 505 ⟩≡
scan_eight_bit_int();
p = box(cur_val);
if (this_if == IF_VOID_CODE) {
b = (p == null);
}
else if (p == null) {
b = false;
}
else if (this_if == IF_HBOX_CODE) {
b = (type(p) == HLIST_NODE);
}
else {
b = (type(p) == VLIST_NODE);
}
Section 506
An active character will be treated as category 13 following \if\noexpand
or following \ifcat\noexpand
.
We use the fact that active characters have the smallest tokens, among all control sequences.
#define get_x_token_or_active_char \
do { \
get_x_token(); \
if (cur_cmd == RELAX && cur_chr == NO_EXPAND_FLAG) { \
cur_cmd = ACTIVE_CHAR; \
cur_chr = cur_tok - CS_TOKEN_FLAG - ACTIVE_BASE; \
} \
} while (0)
⟨ Test if two characters match 506 ⟩≡
get_x_token_or_active_char;
if (cur_cmd > ACTIVE_CHAR ||cur_chr > 255) {
// not a character
m = RELAX;
n = 256;
}
else {
m = cur_cmd;
n = cur_chr;
}
get_x_token_or_active_char;
if (cur_cmd > ACTIVE_CHAR || cur_chr > 255) {
cur_cmd = RELAX;
cur_chr = 256;
}
if (this_if == IF_CHAR_CODE) {
b = (n == cur_chr);
}
else {
b = (m == cur_cmd);
}
Section 507
Note that ‘\ifx
’ will declare two macros different if one is long
or outer and the other isn’t, even though the texts of the macros are the same.
We need to reset scanner_status, since \outer
control sequences are allowed, but we might be scanning a macro definition or preamble.
⟨ Test if two tokens match 507 ⟩≡
save_scanner_status = scanner_status;
scanner_status = NORMAL;
get_next();
n = cur_cs;
p = cur_cmd;
q = cur_chr;
get_next();
if (cur_cmd != p) {
b = false;
}
else if (cur_cmd < CALL) {
b = (cur_chr == q);
}
else {
// << Test if two macro texts match, 508 >>
}
scanner_status = save_scanner_status;
Section 508
Note also that ‘\ifx
’ decides that macros \a
and \b
are different in examples like this:
\def\a{\c}
\def\c{}
\def\b{\d}
\def\d{}
⟨ Test if two macro texts match 508 ⟩≡
p = link(cur_chr);
q = link(equiv(n)); // omit reference counts
if (p == q) {
b = true;
}
else {
while (p != null && q != null) {
if (info(p) != info(q)) {
p = null;
}
else {
p = link(p);
q = link(q);
}
}
b = (p == null && q == null);
}
Section 509
⟨ Select the appropriate case and return or goto common_ending 509 ⟩≡
scan_int();
n = cur_val; // |n| is the number of cases to pass
if (tracing_commands > 1) {
begin_diagnostic();
print("{case ");
print_int(n);
print_char('}');
end_diagnostic(false);
}
while (n != 0) {
pass_text();
if (cond_ptr == save_cond_ptr) {
if (cur_chr == OR_CODE) {
decr(n);
}
else {
goto common_ending;
}
}
else if (cur_chr == FI_CODE) {
// << Pop the condition stack, 496 >>
}
}
change_if_limit(OR_CODE, save_cond_ptr);
return; // wait for \or, \else, or \fi
Section 510
The processing of conditionals is complete except for the following code, which is actually part of expand.
It comes into play when \or
, \else
, or \fi
is scanned.
⟨ Terminate the current conditional and skip to \fi 510 ⟩≡
if (cur_chr > if_limit) {
if (if_limit == IF_CODE) {
insert_relax(); // condition not yet evaluated
}
else {
print_err("Extra ");
print_cmd_chr(FI_OR_ELSE, cur_chr);
help1("I'm ignoring this; it doesn't match any \\if.");
error();
}
}
else {
while (cur_chr != FI_CODE) {
pass_text(); // skip to \fi
}
// << Pop the condition stack, 496 >>
}