Skip to main content

retex_engine/
cond.rs

1//! Conditional processing (*TeX: The Program* §487–§510, plus the e-TeX
2//! and LuaTeX conditionals).
3
4use crate::Engine;
5use crate::cmd::Cmd;
6use crate::hash::{CsKind, NO_CS, UNDEFINED_CS};
7use crate::input::{NO_EXPAND_FLAG, ScannerStatus};
8use crate::primitives_gen::assign_int as ip;
9use crate::scan::ValLevel;
10
11/// `if_code`: the condition has not yet been evaluated.
12pub const IF_CODE: u32 = 1;
13/// `fi_code`.
14pub const FI_CODE: u32 = 2;
15/// `else_code`.
16pub const ELSE_CODE: u32 = 3;
17/// `or_code`.
18pub const OR_CODE: u32 = 4;
19
20/// Offset added to the `if_test` modifier by `\unless`.
21pub const UNLESS_CODE: u32 = 32;
22
23/// `if_test` modifiers (LuaTeX numbering).
24#[allow(missing_docs)]
25pub mod if_codes {
26    pub const IF_CHAR: u32 = 0;
27    pub const IF_CAT: u32 = 1;
28    pub const IF_INT: u32 = 2;
29    pub const IF_DIM: u32 = 3;
30    pub const IF_ODD: u32 = 4;
31    pub const IF_VMODE: u32 = 5;
32    pub const IF_HMODE: u32 = 6;
33    pub const IF_MMODE: u32 = 7;
34    pub const IF_INNER: u32 = 8;
35    pub const IF_VOID: u32 = 9;
36    pub const IF_HBOX: u32 = 10;
37    pub const IF_VBOX: u32 = 11;
38    pub const IFX: u32 = 12;
39    pub const IF_EOF: u32 = 13;
40    pub const IF_TRUE: u32 = 14;
41    pub const IF_FALSE: u32 = 15;
42    pub const IF_CASE: u32 = 16;
43    pub const IF_DEF: u32 = 17;
44    pub const IF_CS: u32 = 18;
45    pub const IF_FONT_CHAR: u32 = 19;
46    pub const IF_IN_CSNAME: u32 = 20;
47    pub const IF_PRIMITIVE: u32 = 21;
48    pub const IF_ABS_NUM: u32 = 22;
49    pub const IF_ABS_DIM: u32 = 23;
50    pub const IF_CONDITION: u32 = 24;
51}
52use if_codes::*;
53
54/// A saved outer conditional.
55#[derive(Clone, Copy, Debug)]
56pub struct CondRecord {
57    /// Its `if_limit`.
58    pub if_limit: u32,
59    /// Its `cur_if`.
60    pub cur_if: u32,
61    /// Its `if_line`.
62    pub if_line: i32,
63}
64
65/// Conditional state.
66#[derive(Debug, Default, Clone)]
67pub struct CondState {
68    /// The condition stack (TeX's `cond_ptr` list, innermost last). Its
69    /// length is the number of active conditionals.
70    pub stack: Vec<CondRecord>,
71    /// What ends the current conditional (`if_limit`).
72    pub if_limit: u32,
73    /// The type of the current conditional (`cur_if`).
74    pub cur_if: u32,
75    /// Line where the current conditional began (`if_line`).
76    pub if_line: i32,
77    /// Line where skipping began (`skip_line`).
78    pub skip_line: i32,
79    /// Whether we are inside `\csname`…`\endcsname` (`is_in_csname`).
80    pub is_in_csname: bool,
81}
82
83impl Engine {
84    /// Skips text until `\fi`, `\else` or `\or` at level 0 (`pass_text`).
85    pub fn pass_text(&mut self) {
86        let save = self.input.scanner_status;
87        self.input.scanner_status = ScannerStatus::Skipping;
88        let mut l = 0i32;
89        self.cond.skip_line = self.input.line();
90        loop {
91            self.get_next();
92            if self.cur_cmd == Cmd::FiOrElse {
93                if l == 0 {
94                    break;
95                }
96                if self.cur_chr == FI_CODE {
97                    l -= 1;
98                }
99            } else if self.cur_cmd == Cmd::IfTest {
100                l += 1;
101            }
102        }
103        self.input.scanner_status = save;
104        if self.eqtb.int(ip::TRACINGIFS) > 0 {
105            self.show_cur_cmd_chr();
106        }
107    }
108
109    fn push_cond(&mut self) {
110        self.cond.stack.push(CondRecord {
111            if_limit: self.cond.if_limit,
112            cur_if: self.cond.cur_if,
113            if_line: self.cond.if_line,
114        });
115        self.cond.cur_if = self.cur_chr;
116        self.cond.if_limit = IF_CODE;
117        self.cond.if_line = self.input.line();
118    }
119
120    /// Pops the condition stack.
121    pub fn pop_cond(&mut self) {
122        if let Some(r) = self.cond.stack.pop() {
123            self.cond.if_line = r.if_line;
124            self.cond.cur_if = r.cur_if;
125            self.cond.if_limit = r.if_limit;
126        }
127    }
128
129    fn change_if_limit(&mut self, l: u32, p: usize) {
130        if p == self.cond.stack.len() {
131            self.cond.if_limit = l;
132        } else if let Some(r) = self.cond.stack.get_mut(p) {
133            r.if_limit = l;
134        } else {
135            self.confusion("if");
136        }
137    }
138
139    /// Like `get_x_token`, but a `\noexpand`ed active character counts as
140    /// that character (`get_x_token_or_active_char`).
141    fn get_x_token_or_active_char(&mut self) {
142        self.get_x_token();
143        if self.cur_cmd == Cmd::Relax
144            && self.cur_chr == NO_EXPAND_FLAG
145            && let CsKind::Active(c) = self.hash.kind(self.cur_tok.cs_id())
146        {
147            self.cur_cmd = Cmd::ParEnd; // active_char
148            self.cur_chr = *c;
149        }
150    }
151
152    /// Expands a conditional (`conditional`, §498).
153    pub fn conditional(&mut self) {
154        if self.eqtb.int(ip::TRACINGIFS) > 0 && self.eqtb.int(ip::TRACINGCOMMANDS) <= 1 {
155            self.show_cur_cmd_chr();
156        }
157        let is_unless = self.cur_chr >= UNLESS_CODE;
158        let this_if = self.cur_chr % UNLESS_CODE;
159        if this_if == IF_CONDITION {
160            // LuaTeX: a no-op that only counts as a conditional when skipped.
161            return;
162        }
163        self.push_cond();
164        let save_cond_ptr = self.cond.stack.len();
165        let mut b: bool;
166        match this_if {
167            IF_CHAR | IF_CAT => {
168                self.get_x_token_or_active_char();
169                let (m, n) = if self.cur_cmd > Cmd::ParEnd || self.cur_chr > 0x10_FFFF {
170                    (Cmd::Relax, 0x11_0000)
171                } else {
172                    (self.cur_cmd, self.cur_chr)
173                };
174                self.get_x_token_or_active_char();
175                if self.cur_cmd > Cmd::ParEnd || self.cur_chr > 0x10_FFFF {
176                    self.cur_cmd = Cmd::Relax;
177                    self.cur_chr = 0x11_0000;
178                }
179                b = if this_if == IF_CHAR {
180                    n == self.cur_chr
181                } else {
182                    m == self.cur_cmd
183                };
184            }
185            IF_INT | IF_DIM | IF_ABS_NUM | IF_ABS_DIM => {
186                let dim = this_if == IF_DIM || this_if == IF_ABS_DIM;
187                let abs = this_if == IF_ABS_NUM || this_if == IF_ABS_DIM;
188                if dim {
189                    self.scan_normal_dimen();
190                } else {
191                    self.scan_int();
192                }
193                let mut n = self.cur_val;
194                if abs {
195                    n = n.wrapping_abs();
196                }
197                // Get the next non-blank non-call token.
198                loop {
199                    self.get_x_token();
200                    if self.cur_cmd != Cmd::Spacer {
201                        break;
202                    }
203                }
204                let r = if self.cur_tok.0 == crate::token::Token::other('<' as u32).0
205                    || self.cur_tok.0 == crate::token::Token::other('=' as u32).0
206                    || self.cur_tok.0 == crate::token::Token::other('>' as u32).0
207                {
208                    self.cur_tok.chr()
209                } else {
210                    self.print_err("Missing = inserted for ");
211                    self.print_cmd_chr(Cmd::IfTest, this_if);
212                    self.help(&["I was expecting to see `<', `=', or `>'. Didn't."]);
213                    self.back_error();
214                    '=' as u32
215                };
216                if dim {
217                    self.scan_normal_dimen();
218                } else {
219                    self.scan_int();
220                }
221                let mut v = self.cur_val;
222                if abs {
223                    v = v.wrapping_abs();
224                }
225                b = match char::from_u32(r) {
226                    Some('<') => n < v,
227                    Some('=') => n == v,
228                    _ => n > v,
229                };
230            }
231            IF_ODD => {
232                self.scan_int();
233                b = self.cur_val % 2 != 0;
234            }
235            IF_VMODE => b = self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical,
236            IF_HMODE => b = self.mode.abs_kind() == crate::maincontrol::ModeKind::Horizontal,
237            IF_MMODE => b = self.mode.abs_kind() == crate::maincontrol::ModeKind::Math,
238            IF_INNER => b = self.mode.is_inner(),
239            IF_VOID | IF_HBOX | IF_VBOX => {
240                self.scan_register_num();
241                let p = self.eqtb.boxes[self.cur_val as usize].val;
242                b = match this_if {
243                    IF_VOID => p.is_null(),
244                    IF_HBOX => !p.is_null() && self.box_is_hlist(p),
245                    _ => !p.is_null() && !self.box_is_hlist(p),
246                };
247            }
248            IFX => {
249                let save = self.input.scanner_status;
250                self.input.scanner_status = ScannerStatus::Normal;
251                self.get_next();
252                let n = self.cur_cs;
253                let p = self.cur_cmd;
254                let q = self.cur_chr;
255                let first_mac = if n != NO_CS {
256                    self.eqtb.meaning(n).mac.clone()
257                } else {
258                    None
259                };
260                self.get_next();
261                b = if self.cur_cmd != p {
262                    false
263                } else if !self.cur_cmd.is_call() {
264                    self.cur_chr == q
265                } else {
266                    let second = if self.cur_cs != NO_CS {
267                        self.eqtb.meaning(self.cur_cs).mac.clone()
268                    } else {
269                        None
270                    };
271                    match (first_mac, second) {
272                        (Some(a), Some(c)) => {
273                            std::rc::Rc::ptr_eq(&a, &c)
274                                || (a.protected == c.protected && a.tokens == c.tokens)
275                        }
276                        _ => false,
277                    }
278                };
279                self.input.scanner_status = save;
280            }
281            IF_EOF => {
282                self.scan_four_bit_int_or_more();
283                b = !self.files.read_open(self.cur_val as u32);
284            }
285            IF_TRUE => b = true,
286            IF_FALSE => b = false,
287            IF_DEF => {
288                let save = self.input.scanner_status;
289                self.input.scanner_status = ScannerStatus::Normal;
290                self.get_next();
291                b = self.cur_cmd != Cmd::UndefinedCs;
292                self.input.scanner_status = save;
293            }
294            IF_CS => {
295                let e = self.cond.is_in_csname;
296                self.cond.is_in_csname = true;
297                let mut name: Vec<u8> = Vec::new();
298                loop {
299                    self.get_x_token();
300                    if self.cur_cs != NO_CS {
301                        break;
302                    }
303                    crate::utf8x::push(&mut name, self.cur_chr);
304                }
305                if self.cur_cmd != Cmd::EndCsName {
306                    self.complain_missing_endcsname();
307                }
308                let cs = self.hash.lookup(&name).unwrap_or(UNDEFINED_CS);
309                self.cur_cs = cs;
310                b = self.eqtb.meaning(cs).cmd != Cmd::UndefinedCs;
311                self.lookup_named_cs(&name);
312                self.cond.is_in_csname = e;
313            }
314            IF_FONT_CHAR => {
315                self.scan_font_ident();
316                let f = self.cur_val as u32;
317                self.scan_char_num();
318                b = self.font_has_char(f, self.cur_val as u32);
319            }
320            IF_IN_CSNAME => b = self.cond.is_in_csname,
321            IF_PRIMITIVE => {
322                let save = self.input.scanner_status;
323                self.input.scanner_status = ScannerStatus::Normal;
324                self.get_next();
325                b = self.cur_cs != NO_CS && self.has_primitive_meaning(self.cur_cs);
326                self.input.scanner_status = save;
327            }
328            IF_CASE => {
329                self.scan_int();
330                let mut n = self.cur_val;
331                if self.eqtb.int(ip::TRACINGCOMMANDS) > 1 {
332                    self.begin_diagnostic();
333                    self.printer.print_str("{case ");
334                    self.printer.print_int(n.into());
335                    self.printer.print_char('}' as u32);
336                    self.end_diagnostic(false);
337                }
338                let mut ended = false;
339                while n != 0 {
340                    self.pass_text();
341                    if self.cond.stack.len() == save_cond_ptr {
342                        if self.cur_chr == OR_CODE {
343                            n -= 1;
344                        } else {
345                            ended = true;
346                            break;
347                        }
348                    } else if self.cur_chr == FI_CODE {
349                        self.pop_cond();
350                    }
351                }
352                if !ended {
353                    self.change_if_limit(OR_CODE, save_cond_ptr);
354                    return;
355                }
356                self.common_ending();
357                return;
358            }
359            _ => b = false,
360        }
361        let _ = ValLevel::Int;
362        if is_unless {
363            b = !b;
364        }
365        if self.eqtb.int(ip::TRACINGCOMMANDS) > 1 {
366            self.begin_diagnostic();
367            self.printer.print_str(if b { "{true}" } else { "{false}" });
368            self.end_diagnostic(false);
369        }
370        if b {
371            self.change_if_limit(ELSE_CODE, save_cond_ptr);
372            return;
373        }
374        // Skip to \else or \fi.
375        loop {
376            self.pass_text();
377            if self.cond.stack.len() == save_cond_ptr {
378                if self.cur_chr != OR_CODE {
379                    break;
380                }
381                self.print_err("Extra ");
382                self.print_esc_str("or");
383                self.help(&["I'm ignoring this; it doesn't match any \\if."]);
384                self.error();
385            } else if self.cur_chr == FI_CODE {
386                self.pop_cond();
387            }
388        }
389        self.common_ending();
390    }
391
392    fn common_ending(&mut self) {
393        if self.cur_chr == FI_CODE {
394            self.pop_cond();
395        } else {
396            self.cond.if_limit = FI_CODE;
397        }
398    }
399
400    /// Handles `\fi`, `\else` and `\or` met during expansion (§510).
401    pub fn fi_or_else(&mut self) {
402        if self.eqtb.int(ip::TRACINGIFS) > 0 && self.eqtb.int(ip::TRACINGCOMMANDS) <= 1 {
403            self.show_cur_cmd_chr();
404        }
405        if self.cur_chr > self.cond.if_limit {
406            if self.cond.if_limit == IF_CODE {
407                self.insert_relax();
408            } else {
409                self.print_err("Extra ");
410                self.print_cmd_chr(Cmd::FiOrElse, self.cur_chr);
411                self.help(&["I'm ignoring this; it doesn't match any \\if."]);
412                self.error();
413            }
414        } else {
415            while self.cur_chr != FI_CODE {
416                self.pass_text();
417            }
418            self.pop_cond();
419        }
420    }
421
422    /// Whether `cs` currently has the meaning of the primitive of the same
423    /// name (`\ifprimitive`).
424    pub fn has_primitive_meaning(&self, cs: crate::hash::CsId) -> bool {
425        let name = self.hash.name(cs);
426        match crate::primitives::lookup(name) {
427            Some(p) => {
428                let m = self.eqtb.meaning(cs);
429                m.cmd == p.cmd && m.chr == p.chr && m.mac.is_none()
430            }
431            None => false,
432        }
433    }
434}