1use crate::Engine;
5use crate::cmd::Cmd;
6use crate::hash::{CsKind, NO_CS, UNDEFINED_CS};
7use crate::input::{Level, 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.skip_plain_tokens();
92 self.get_next();
93 if self.cur_cmd == Cmd::FiOrElse {
94 if l == 0 {
95 break;
96 }
97 if self.cur_chr == FI_CODE {
98 l -= 1;
99 }
100 } else if self.cur_cmd == Cmd::IfTest {
101 l += 1;
102 }
103 }
104 self.input.scanner_status = save;
105 if self.eqtb.int(ip::TRACINGIFS) > 0 {
106 self.show_cur_cmd_chr();
107 }
108 }
109
110 fn skip_plain_tokens(&mut self) {
116 let Some(Level::Tokens(t)) = self.input.stack.last_mut() else {
117 return;
118 };
119 let mut loc = t.loc;
120 let mut align = 0i32;
121 while let Some(&tok) = t.list.get(loc) {
122 if tok.is_cs() {
123 let m = self.eqtb.meaning(tok.cs_id());
124 let plain =
125 m.cmd < Cmd::TabMark || (m.cmd > Cmd::CarRet && !m.cmd.is_outer_or_special());
126 if !plain || m.cmd == Cmd::FiOrElse || m.cmd == Cmd::IfTest {
127 break;
128 }
129 } else {
130 let cmd = tok.cmd();
131 if !(cmd > Cmd::CarRet || cmd < Cmd::MathShift) {
132 break;
133 }
134 match cmd {
135 Cmd::LeftBrace => align += 1,
136 Cmd::RightBrace => align -= 1,
137 _ => {}
138 }
139 }
140 loc += 1;
141 }
142 t.loc = loc;
143 self.input.align_state += align;
144 }
145
146 fn push_cond(&mut self) {
147 self.cond.stack.push(CondRecord {
148 if_limit: self.cond.if_limit,
149 cur_if: self.cond.cur_if,
150 if_line: self.cond.if_line,
151 });
152 self.cond.cur_if = self.cur_chr;
153 self.cond.if_limit = IF_CODE;
154 self.cond.if_line = self.input.line();
155 }
156
157 pub fn pop_cond(&mut self) {
159 if let Some(r) = self.cond.stack.pop() {
160 self.cond.if_line = r.if_line;
161 self.cond.cur_if = r.cur_if;
162 self.cond.if_limit = r.if_limit;
163 }
164 }
165
166 fn change_if_limit(&mut self, l: u32, p: usize) {
167 if p == self.cond.stack.len() {
168 self.cond.if_limit = l;
169 } else if let Some(r) = self.cond.stack.get_mut(p) {
170 r.if_limit = l;
171 } else {
172 self.confusion("if");
173 }
174 }
175
176 fn get_x_token_or_active_char(&mut self) {
179 self.get_x_token();
180 if self.cur_cmd == Cmd::Relax
181 && self.cur_chr == NO_EXPAND_FLAG
182 && let CsKind::Active(c) = self.hash.kind(self.cur_tok.cs_id())
183 {
184 self.cur_cmd = Cmd::ParEnd; self.cur_chr = *c;
186 }
187 }
188
189 pub fn conditional(&mut self) {
191 if self.eqtb.int(ip::TRACINGIFS) > 0 && self.eqtb.int(ip::TRACINGCOMMANDS) <= 1 {
192 self.show_cur_cmd_chr();
193 }
194 let is_unless = self.cur_chr >= UNLESS_CODE;
195 let this_if = self.cur_chr % UNLESS_CODE;
196 if this_if == IF_CONDITION {
197 return;
199 }
200 self.push_cond();
201 let save_cond_ptr = self.cond.stack.len();
202 let mut b: bool;
203 match this_if {
204 IF_CHAR | IF_CAT => {
205 self.get_x_token_or_active_char();
206 let (m, n) = if self.cur_cmd > Cmd::ParEnd || self.cur_chr > 0x10_FFFF {
207 (Cmd::Relax, 0x11_0000)
208 } else {
209 (self.cur_cmd, self.cur_chr)
210 };
211 self.get_x_token_or_active_char();
212 if self.cur_cmd > Cmd::ParEnd || self.cur_chr > 0x10_FFFF {
213 self.cur_cmd = Cmd::Relax;
214 self.cur_chr = 0x11_0000;
215 }
216 b = if this_if == IF_CHAR {
217 n == self.cur_chr
218 } else {
219 m == self.cur_cmd
220 };
221 }
222 IF_INT | IF_DIM | IF_ABS_NUM | IF_ABS_DIM => {
223 let dim = this_if == IF_DIM || this_if == IF_ABS_DIM;
224 let abs = this_if == IF_ABS_NUM || this_if == IF_ABS_DIM;
225 if dim {
226 self.scan_normal_dimen();
227 } else {
228 self.scan_int();
229 }
230 let mut n = self.cur_val;
231 if abs {
232 n = n.wrapping_abs();
233 }
234 loop {
236 self.get_x_token();
237 if self.cur_cmd != Cmd::Spacer {
238 break;
239 }
240 }
241 let r = if self.cur_tok.0 == crate::token::Token::other('<' as u32).0
242 || self.cur_tok.0 == crate::token::Token::other('=' as u32).0
243 || self.cur_tok.0 == crate::token::Token::other('>' as u32).0
244 {
245 self.cur_tok.chr()
246 } else {
247 self.print_err("Missing = inserted for ");
248 self.print_cmd_chr(Cmd::IfTest, this_if);
249 self.help(&["I was expecting to see `<', `=', or `>'. Didn't."]);
250 self.back_error();
251 '=' as u32
252 };
253 if dim {
254 self.scan_normal_dimen();
255 } else {
256 self.scan_int();
257 }
258 let mut v = self.cur_val;
259 if abs {
260 v = v.wrapping_abs();
261 }
262 b = match char::from_u32(r) {
263 Some('<') => n < v,
264 Some('=') => n == v,
265 _ => n > v,
266 };
267 }
268 IF_ODD => {
269 self.scan_int();
270 b = self.cur_val % 2 != 0;
271 }
272 IF_VMODE => b = self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical,
273 IF_HMODE => b = self.mode.abs_kind() == crate::maincontrol::ModeKind::Horizontal,
274 IF_MMODE => b = self.mode.abs_kind() == crate::maincontrol::ModeKind::Math,
275 IF_INNER => b = self.mode.is_inner(),
276 IF_VOID | IF_HBOX | IF_VBOX => {
277 self.scan_register_num();
278 let p = self.eqtb.boxes[self.cur_val as usize].val;
279 b = match this_if {
280 IF_VOID => p.is_null(),
281 IF_HBOX => !p.is_null() && self.box_is_hlist(p),
282 _ => !p.is_null() && !self.box_is_hlist(p),
283 };
284 }
285 IFX => {
286 let save = self.input.scanner_status;
287 self.input.scanner_status = ScannerStatus::Normal;
288 self.get_next();
289 let n = self.cur_cs;
290 let p = self.cur_cmd;
291 let q = self.cur_chr;
292 let first_mac = if n != NO_CS {
293 self.eqtb.meaning(n).mac.clone()
294 } else {
295 None
296 };
297 self.get_next();
298 b = if self.cur_cmd != p {
299 false
300 } else if !self.cur_cmd.is_call() {
301 self.cur_chr == q
302 } else {
303 let second = if self.cur_cs != NO_CS {
304 self.eqtb.meaning(self.cur_cs).mac.clone()
305 } else {
306 None
307 };
308 match (first_mac, second) {
309 (Some(a), Some(c)) => {
310 std::rc::Rc::ptr_eq(&a, &c)
311 || (a.protected == c.protected && a.tokens == c.tokens)
312 }
313 _ => false,
314 }
315 };
316 self.input.scanner_status = save;
317 }
318 IF_EOF => {
319 self.scan_four_bit_int_or_more();
320 b = !self.files.read_open(self.cur_val as u32);
321 }
322 IF_TRUE => b = true,
323 IF_FALSE => b = false,
324 IF_DEF => {
325 let save = self.input.scanner_status;
326 self.input.scanner_status = ScannerStatus::Normal;
327 self.get_next();
328 b = self.cur_cmd != Cmd::UndefinedCs;
329 self.input.scanner_status = save;
330 }
331 IF_CS => {
332 let e = self.cond.is_in_csname;
333 self.cond.is_in_csname = true;
334 let mut name: Vec<u8> = Vec::new();
335 loop {
336 self.get_x_token();
337 if self.cur_cs != NO_CS {
338 break;
339 }
340 crate::utf8x::push(&mut name, self.cur_chr);
341 }
342 if self.cur_cmd != Cmd::EndCsName {
343 self.complain_missing_endcsname();
344 }
345 let cs = self.hash.lookup(&name).unwrap_or(UNDEFINED_CS);
346 self.cur_cs = cs;
347 b = self.eqtb.meaning(cs).cmd != Cmd::UndefinedCs;
348 self.lookup_named_cs(&name);
349 self.cond.is_in_csname = e;
350 }
351 IF_FONT_CHAR => {
352 self.scan_font_ident();
353 let f = self.cur_val as u32;
354 self.scan_char_num();
355 b = self.font_has_char(f, self.cur_val as u32);
356 }
357 IF_IN_CSNAME => b = self.cond.is_in_csname,
358 IF_PRIMITIVE => {
359 let save = self.input.scanner_status;
360 self.input.scanner_status = ScannerStatus::Normal;
361 self.get_next();
362 b = self.cur_cs != NO_CS && self.has_primitive_meaning(self.cur_cs);
363 self.input.scanner_status = save;
364 }
365 IF_CASE => {
366 self.scan_int();
367 let mut n = self.cur_val;
368 if self.eqtb.int(ip::TRACINGCOMMANDS) > 1 {
369 self.begin_diagnostic();
370 self.printer.print_str("{case ");
371 self.printer.print_int(n.into());
372 self.printer.print_char('}' as u32);
373 self.end_diagnostic(false);
374 }
375 let mut ended = false;
376 while n != 0 {
377 self.pass_text();
378 if self.cond.stack.len() == save_cond_ptr {
379 if self.cur_chr == OR_CODE {
380 n -= 1;
381 } else {
382 ended = true;
383 break;
384 }
385 } else if self.cur_chr == FI_CODE {
386 self.pop_cond();
387 }
388 }
389 if !ended {
390 self.change_if_limit(OR_CODE, save_cond_ptr);
391 return;
392 }
393 self.common_ending();
394 return;
395 }
396 _ => b = false,
397 }
398 let _ = ValLevel::Int;
399 if is_unless {
400 b = !b;
401 }
402 if self.eqtb.int(ip::TRACINGCOMMANDS) > 1 {
403 self.begin_diagnostic();
404 self.printer.print_str(if b { "{true}" } else { "{false}" });
405 self.end_diagnostic(false);
406 }
407 if b {
408 self.change_if_limit(ELSE_CODE, save_cond_ptr);
409 return;
410 }
411 loop {
413 self.pass_text();
414 if self.cond.stack.len() == save_cond_ptr {
415 if self.cur_chr != OR_CODE {
416 break;
417 }
418 self.print_err("Extra ");
419 self.print_esc_str("or");
420 self.help(&["I'm ignoring this; it doesn't match any \\if."]);
421 self.error();
422 } else if self.cur_chr == FI_CODE {
423 self.pop_cond();
424 }
425 }
426 self.common_ending();
427 }
428
429 fn common_ending(&mut self) {
430 if self.cur_chr == FI_CODE {
431 self.pop_cond();
432 } else {
433 self.cond.if_limit = FI_CODE;
434 }
435 }
436
437 pub fn fi_or_else(&mut self) {
439 if self.eqtb.int(ip::TRACINGIFS) > 0 && self.eqtb.int(ip::TRACINGCOMMANDS) <= 1 {
440 self.show_cur_cmd_chr();
441 }
442 if self.cur_chr > self.cond.if_limit {
443 if self.cond.if_limit == IF_CODE {
444 self.insert_relax();
445 } else {
446 self.print_err("Extra ");
447 self.print_cmd_chr(Cmd::FiOrElse, self.cur_chr);
448 self.help(&["I'm ignoring this; it doesn't match any \\if."]);
449 self.error();
450 }
451 } else {
452 while self.cur_chr != FI_CODE {
453 self.pass_text();
454 }
455 self.pop_cond();
456 }
457 }
458
459 pub fn has_primitive_meaning(&self, cs: crate::hash::CsId) -> bool {
462 let name = self.hash.name(cs);
463 match crate::primitives::lookup(name) {
464 Some(p) => {
465 let m = self.eqtb.meaning(cs);
466 m.cmd == p.cmd && m.chr == p.chr && m.mac.is_none()
467 }
468 None => false,
469 }
470 }
471}