1use 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
11pub const IF_CODE: u32 = 1;
13pub const FI_CODE: u32 = 2;
15pub const ELSE_CODE: u32 = 3;
17pub const OR_CODE: u32 = 4;
19
20pub const UNLESS_CODE: u32 = 32;
22
23#[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#[derive(Clone, Copy, Debug)]
56pub struct CondRecord {
57 pub if_limit: u32,
59 pub cur_if: u32,
61 pub if_line: i32,
63}
64
65#[derive(Debug, Default, Clone)]
67pub struct CondState {
68 pub stack: Vec<CondRecord>,
71 pub if_limit: u32,
73 pub cur_if: u32,
75 pub if_line: i32,
77 pub skip_line: i32,
79 pub is_in_csname: bool,
81}
82
83impl Engine {
84 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 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 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; self.cur_chr = *c;
149 }
150 }
151
152 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 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 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 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 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 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}