1% conditional.w
2%
3% Copyright 2009-2010 Taco Hoekwater <taco@@luatex.org>
4%
5% This file is part of LuaTeX.
6%
7% LuaTeX is free software; you can redistribute it and/or modify it under
8% the terms of the GNU General Public License as published by the Free
9% Software Foundation; either version 2 of the License, or (at your
10% option) any later version.
11%
12% LuaTeX is distributed in the hope that it will be useful, but WITHOUT
13% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14% FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
15% License for more details.
16%
17% You should have received a copy of the GNU General Public License along
18% with LuaTeX; if not, see <http://www.gnu.org/licenses/>.
19
20@ @c
21
22
23#include "ptexlib.h"
24
25@ @c
26#define box(A) eqtb[box_base+(A)].hh.rh
27
28@* We consider now the way \TeX\ handles various kinds of \.{\\if} commands.
29
30@ Conditions can be inside conditions, and this nesting has a stack
31that is independent of the |save_stack|.
32
33Four global variables represent the top of the condition stack:
34|cond_ptr| points to pushed-down entries, if any; |if_limit| specifies
35the largest code of a |fi_or_else| command that is syntactically legal;
36|cur_if| is the name of the current type of conditional; and |if_line|
37is the line number at which it began.
38
39If no conditions are currently in progress, the condition stack has the
40special state |cond_ptr=null|, |if_limit=normal|, |cur_if=0|, |if_line=0|.
41Otherwise |cond_ptr| points to a two-word node; the |type|, |subtype|, and
42|link| fields of the first word contain |if_limit|, |cur_if|, and
43|cond_ptr| at the next level, and the second word contains the
44corresponding |if_line|.
45
46@c
47halfword cond_ptr;              /* top of the condition stack */
48int if_limit;                   /* upper bound on |fi_or_else| codes */
49int cur_if;                     /* type of conditional being worked on */
50int if_line;                    /* line where that conditional began */
51
52
53@ When we skip conditional text, we keep track of the line number
54where skipping began, for use in error messages.
55
56@c
57int skip_line;                  /* skipping began here */
58
59
60@ Here is a procedure that ignores text until coming to an \.{\\or},
61\.{\\else}, or \.{\\fi} at level zero of $\.{\\if}\ldots\.{\\fi}$
62nesting. After it has acted, |cur_chr| will indicate the token that
63was found, but |cur_tok| will not be set (because this makes the
64procedure run faster).
65
66@c
67void pass_text(void)
68{
69    int l;                      /* level of $\.{\\if}\ldots\.{\\fi}$ nesting */
70    int save_scanner_status;    /* |scanner_status| upon entry */
71    save_scanner_status = scanner_status;
72    scanner_status = skipping;
73    l = 0;
74    skip_line = line;
75    while (1) {
76        get_token_lua();
77        if (cur_cmd == fi_or_else_cmd) {
78            if (l == 0)
79                break;
80            if (cur_chr == fi_code)
81                decr(l);
82        } else if (cur_cmd == if_test_cmd) {
83            incr(l);
84        }
85    }
86    scanner_status = save_scanner_status;
87    if (int_par(tracing_ifs_code) > 0)
88        show_cur_cmd_chr();
89}
90
91
92@ When we begin to process a new \.{\\if}, we set |if_limit:=if_code|; then
93if\/ \.{\\or} or \.{\\else} or \.{\\fi} occurs before the current \.{\\if}
94condition has been evaluated, \.{\\relax} will be inserted.
95For example, a sequence of commands like `\.{\\ifvoid1\\else...\\fi}'
96would otherwise require something after the `\.1'.
97
98@c
99void push_condition_stack(void)
100{
101    halfword p = new_node(if_node, 0);
102    vlink(p) = cond_ptr;
103    if_limit_type(p) = (quarterword) if_limit;
104    if_limit_subtype(p) = (quarterword) cur_if;
105    if_line_field(p) = if_line;
106    cond_ptr = p;
107    cur_if = cur_chr;
108    if_limit = if_code;
109    if_line = line;
110}
111
112void pop_condition_stack(void)
113{
114    halfword p;
115    if (if_stack[in_open] == cond_ptr)
116        if_warning();
117    /* conditionals possibly not properly nested with files */
118    p = cond_ptr;
119    if_line = if_line_field(p);
120    cur_if = if_limit_subtype(p);
121    if_limit = if_limit_type(p);
122    cond_ptr = vlink(p);
123    flush_node(p);
124}
125
126
127@ Here's a procedure that changes the |if_limit| code corresponding to
128a given value of |cond_ptr|.
129
130@c
131void change_if_limit(int l, halfword p)
132{
133    halfword q;
134    if (p == cond_ptr) {
135        if_limit = l;           /* that's the easy case */
136    } else {
137        q = cond_ptr;
138        while (1) {
139            if (q == null)
140                confusion("if");
141            if (vlink(q) == p) {
142                if_limit_type(q) = (quarterword) l;
143                return;
144            }
145            q = vlink(q);
146        }
147    }
148}
149
150
151@ The conditional \.{\\ifcsname} is equivalent to \.{\\expandafter}
152\.{\\expandafter} \.{\\ifdefined} \.{\\csname}, except that no new
153control sequence will be entered into the hash table (once all tokens
154preceding the mandatory \.{\\endcsname} have been expanded).
155
156@c
157static boolean test_for_cs(void)
158{
159    boolean b;                  /*is the condition true? */
160    int m, s;                   /*to be tested against the second operand */
161    halfword n, p, q;           /*for traversing token lists in \.{\\ifx} tests */
162    n = get_avail();
163    p = n;                      /*head of the list of characters */
164    b = false;
165    while (1) {
166        get_x_token();
167        if (cur_cs != 0)
168            break;
169        store_new_token(cur_tok);
170    }
171    if (cur_cmd != end_cs_name_cmd) {
172        if (int_par(suppress_ifcsname_error_code)) {
173            do {
174                get_x_token();
175            } while (cur_cmd != end_cs_name_cmd);
176            flush_list(n);
177            return b;
178        } else {
179            complain_missing_csname();
180        }
181    }
182    /* Look up the characters of list |n| in the hash table, and set |cur_cs| */
183    m = first;
184    p = token_link(n);
185    while (p != null) {
186        if (m >= max_buf_stack) {
187            max_buf_stack = m + 4;
188            if (max_buf_stack >= buf_size)
189                check_buffer_overflow(max_buf_stack);
190        }
191        s = token_chr(token_info(p));
192        if (s <= 0x7F) {
193            buffer[m++] = (packed_ASCII_code) s;
194        } else if (s <= 0x7FF) {
195            buffer[m++] = (packed_ASCII_code) (0xC0 + s / 0x40);
196            buffer[m++] = (packed_ASCII_code) (0x80 + s % 0x40);
197        } else if (s <= 0xFFFF) {
198            buffer[m++] = (packed_ASCII_code) (0xE0 + s / 0x1000);
199            buffer[m++] = (packed_ASCII_code) (0x80 + (s % 0x1000) / 0x40);
200            buffer[m++] = (packed_ASCII_code) (0x80 + (s % 0x1000) % 0x40);
201        } else {
202            buffer[m++] = (packed_ASCII_code) (0xF0 + s / 0x40000);
203            buffer[m++] = (packed_ASCII_code) (0x80 + (s % 0x40000) / 0x1000);
204            buffer[m++] =
205                (packed_ASCII_code) (0x80 + ((s % 0x40000) % 0x1000) / 0x40);
206            buffer[m++] =
207                (packed_ASCII_code) (0x80 + ((s % 0x40000) % 0x1000) % 0x40);
208        }
209        p = token_link(p);
210    }
211    if (m > first) {
212        cur_cs = id_lookup(first, m - first);   /*|no_new_control_sequence| is |true| */
213    } else if (m == first) {
214        cur_cs = null_cs;       /*the list is empty */
215    }
216    b = (eq_type(cur_cs) != undefined_cs_cmd);
217    flush_list(n);
218    return b;
219}
220
221
222@ An active character will be treated as category 13 following
223\.{\\if\\noexpand} or following \.{\\ifcat\\noexpand}.
224
225@c
226#define get_x_token_or_active_char() do {                             \
227        get_x_token();                                                  \
228        if (cur_cmd==relax_cmd && cur_chr==no_expand_flag) {            \
229            if (is_active_cs(cs_text(cur_cs))) {                        \
230                cur_cmd=active_char_cmd;                                \
231                cur_chr=active_cs_value(cs_text(cur_tok-cs_token_flag));	\
232            }                                                           \
233        }                                                               \
234    } while (0)
235
236
237
238
239
240@ A condition is started when the |expand| procedure encounters
241an |if_test| command; in that case |expand| reduces to |conditional|,
242which is a recursive procedure.
243@^recursion@>
244
245@c
246void conditional(void)
247{
248    boolean b;                  /*is the condition true? */
249    int r;                      /*relation to be evaluated */
250    int m, n;                   /*to be tested against the second operand */
251    halfword p, q;              /*for traversing token lists in \.{\\ifx} tests */
252    int save_scanner_status;    /*|scanner_status| upon entry */
253    halfword save_cond_ptr;     /*|cond_ptr| corresponding to this conditional */
254    int this_if;                /*type of this conditional */
255    boolean is_unless;          /*was this if preceded by `\.{\\unless}' ? */
256    b = false;                  /*default is false, just in case */
257    if (int_par(tracing_ifs_code) > 0)
258        if (int_par(tracing_commands_code) <= 1)
259            show_cur_cmd_chr();
260    push_condition_stack();
261    save_cond_ptr = cond_ptr;
262    is_unless = (cur_chr >= unless_code);
263    this_if = cur_chr % unless_code;
264    /* Either process \.{\\ifcase} or set |b| to the value of a boolean condition */
265    switch (this_if) {
266    case if_char_code:
267    case if_cat_code:
268        /* Test if two characters match */
269        get_x_token_or_active_char();
270        if ((cur_cmd > active_char_cmd) || (cur_chr > biggest_char)) {  /*not a character */
271            m = relax_cmd;
272            n = too_big_char;
273        } else {
274            m = cur_cmd;
275            n = cur_chr;
276        }
277        get_x_token_or_active_char();
278        if ((cur_cmd > active_char_cmd) || (cur_chr > biggest_char)) {
279            cur_cmd = relax_cmd;
280            cur_chr = too_big_char;
281        }
282        if (this_if == if_char_code)
283            b = (n == cur_chr);
284        else
285            b = (m == cur_cmd);
286        break;
287    case if_int_code:
288    case if_dim_code:
289    case if_abs_dim_code:
290    case if_abs_num_code:
291        /* Test relation between integers or dimensions */
292        /* Here we use the fact that |"<"|, |"="|, and |">"| are consecutive ASCII
293           codes. */
294        if (this_if == if_int_code || this_if == if_abs_num_code)
295            scan_int();
296        else
297            scan_normal_dimen();
298        n = cur_val;
299        if (n < 0)
300            if (this_if == if_abs_dim_code || this_if == if_abs_num_code)
301                negate(n);
302
303        /* Get the next non-blank non-call... */
304        do {
305            get_x_token();
306        } while (cur_cmd == spacer_cmd);
307
308        if ((cur_tok >= other_token + '<') && (cur_tok <= other_token + '>')) {
309            r = cur_tok - other_token;
310        } else {
311            print_err("Missing = inserted for ");
312            print_cmd_chr(if_test_cmd, this_if);
313            help1("I was expecting to see `<', `=', or `>'. Didn't.");
314            back_error();
315            r = '=';
316        }
317        if (this_if == if_int_code || this_if == if_abs_num_code)
318            scan_int();
319        else
320            scan_normal_dimen();
321
322        if (cur_val < 0)
323            if (this_if == if_abs_dim_code || this_if == if_abs_num_code)
324                negate(cur_val);
325
326        switch (r) {
327        case '<':
328            b = (n < cur_val);
329            break;
330        case '=':
331            b = (n == cur_val);
332            break;
333        case '>':
334            b = (n > cur_val);
335            break;
336        default:
337            b = false;
338            break;              /*can't happen */
339        }
340        break;
341    case if_odd_code:
342        /* Test if an integer is odd */
343        scan_int();
344        b = odd(cur_val);
345        break;
346    case if_vmode_code:
347        b = (abs(cur_list.mode_field) == vmode);
348        break;
349    case if_hmode_code:
350        b = (abs(cur_list.mode_field) == hmode);
351        break;
352    case if_mmode_code:
353        b = (abs(cur_list.mode_field) == mmode);
354        break;
355    case if_inner_code:
356        b = (cur_list.mode_field < 0);
357        break;
358    case if_void_code:
359    case if_hbox_code:
360    case if_vbox_code:
361        /* Test box register status */
362        scan_register_num();
363        p = box(cur_val);
364        if (this_if == if_void_code)
365            b = (p == null);
366        else if (p == null)
367            b = false;
368        else if (this_if == if_hbox_code)
369            b = (type(p) == hlist_node);
370        else
371            b = (type(p) == vlist_node);
372        break;
373    case ifx_code:
374        /* Test if two tokens match */
375        /* Note that `\.{\\ifx}' will declare two macros different if one is \\{long}
376           or \\{outer} and the other isn't, even though the texts of the macros are
377           the same.
378
379           We need to reset |scanner_status|, since \.{\\outer} control sequences
380           are allowed, but we might be scanning a macro definition or preamble.
381         */
382        save_scanner_status = scanner_status;
383        scanner_status = normal;
384        get_token_lua();
385        n = cur_cs;
386        p = cur_cmd;
387        q = cur_chr;
388        get_token_lua();
389        if (cur_cmd != p) {
390            b = false;
391        } else if (cur_cmd < call_cmd) {
392            b = (cur_chr == q);
393        } else {
394            /* Test if two macro texts match */
395            /* Note also that `\.{\\ifx}' decides that macros \.{\\a} and \.{\\b} are
396               different in examples like this:
397               $$\vbox{\halign{\.{#}\hfil&\qquad\.{#}\hfil\cr
398               {}\\def\\a\{\\c\}&
399               {}\\def\\c\{\}\cr
400               {}\\def\\b\{\\d\}&
401               {}\\def\\d\{\}\cr}}$$ */
402            p = token_link(cur_chr);
403            q = token_link(equiv(n));   /*omit reference counts */
404            if (p == q) {
405                b = true;
406            } else {
407                while ((p != null) && (q != null)) {
408                    if (token_info(p) != token_info(q)) {
409                        p = null;
410                    } else {
411                        p = token_link(p);
412                        q = token_link(q);
413                    }
414                }
415                b = ((p == null) && (q == null));
416            }
417        }
418        scanner_status = save_scanner_status;
419        break;
420    case if_eof_code:
421        scan_four_bit_int_or_18();
422        if (cur_val == 18)
423            b = !shellenabledp;
424        else
425            b = (read_open[cur_val] == closed);
426        break;
427    case if_true_code:
428        b = true;
429        break;
430    case if_false_code:
431        b = false;
432        break;
433    case if_case_code:
434        /* Select the appropriate case
435           and |return| or |goto common_ending| */
436        scan_int();
437        n = cur_val;            /*|n| is the number of cases to pass */
438        if (int_par(tracing_commands_code) > 1) {
439            begin_diagnostic();
440            tprint("{case ");
441            print_int(n);
442            print_char('}');
443            end_diagnostic(false);
444        }
445        while (n != 0) {
446            pass_text();
447            if (cond_ptr == save_cond_ptr) {
448                if (cur_chr == or_code)
449                    decr(n);
450                else
451                    goto COMMON_ENDING;
452            } else if (cur_chr == fi_code) {
453                pop_condition_stack();
454            }
455        }
456        change_if_limit(or_code, save_cond_ptr);
457        return;                 /*wait for \.{\\or}, \.{\\else}, or \.{\\fi} */
458        break;
459    case if_primitive_code:
460        save_scanner_status = scanner_status;
461        scanner_status = normal;
462        get_token_lua();
463        scanner_status = save_scanner_status;
464        m = prim_lookup(cs_text(cur_cs));
465        b = ((cur_cmd != undefined_cs_cmd) &&
466             (m != undefined_primitive) &&
467             (cur_cmd == get_prim_eq_type(m)) &&
468             (cur_chr == get_prim_equiv(m)));
469        break;
470    case if_def_code:
471        /* The conditional \.{\\ifdefined} tests if a control sequence is defined. */
472        /* We need to reset |scanner_status|, since \.{\\outer} control sequences
473           are allowed, but we might be scanning a macro definition or preamble. */
474        save_scanner_status = scanner_status;
475        scanner_status = normal;
476        get_token_lua();
477        b = (cur_cmd != undefined_cs_cmd);
478        scanner_status = save_scanner_status;
479        break;
480    case if_cs_code:
481        b = test_for_cs();
482        break;
483    case if_in_csname_code:
484        b = is_in_csname;
485        break;
486    case if_font_char_code:
487        /* The conditional \.{\\iffontchar} tests the existence of a character in
488           a font. */
489        scan_font_ident();
490        n = cur_val;
491        scan_char_num();
492        b = char_exists(n, cur_val);
493        break;
494    default:                   /* there are no other cases, but for -Wall:  */
495        b = false;
496    }
497
498    if (is_unless)
499        b = !b;
500    if (int_par(tracing_commands_code) > 1) {
501        /* Display the value of |b| */
502        begin_diagnostic();
503        if (b)
504            tprint("{true}");
505        else
506            tprint("{false}");
507        end_diagnostic(false);
508    }
509    if (b) {
510        change_if_limit(else_code, save_cond_ptr);
511        return;                 /*wait for \.{\\else} or \.{\\fi} */
512    }
513    /* Skip to \.{\\else} or \.{\\fi}, then |goto common_ending| */
514    /*
515       In a construction like `\.{\\if\\iftrue abc\\else d\\fi}', the first
516       \.{\\else} that we come to after learning that the \.{\\if} is false is
517       not the \.{\\else} we're looking for. Hence the following curious
518       logic is needed.
519     */
520    while (1) {
521        pass_text();
522        if (cond_ptr == save_cond_ptr) {
523            if (cur_chr != or_code)
524                goto COMMON_ENDING;
525            print_err("Extra \\or");
526            help1("I'm ignoring this; it doesn't match any \\if.");
527            error();
528        } else if (cur_chr == fi_code) {
529            pop_condition_stack();
530        }
531    }
532  COMMON_ENDING:
533    if (cur_chr == fi_code)
534        pop_condition_stack();
535    else
536        if_limit = fi_code;     /*wait for \.{\\fi} */
537}
538