Skip to main content

retex_engine/
meaning.rs

1//! Printing control sequences, token lists and meanings (*TeX: The
2//! Program* §262–§299).
3
4use crate::Engine;
5use crate::cmd::Cmd;
6use crate::hash::{CsId, CsKind};
7use crate::primitives_gen as pg;
8use crate::scan::REG_OFFSET;
9use crate::token::Token;
10
11impl Engine {
12    /// Prints the escape character (if valid) followed by `s`
13    /// (`print_esc`).
14    pub fn print_esc_bytes(&mut self, s: &[u8]) {
15        let c = self.eqtb.int(pg::assign_int::ESCAPECHAR);
16        if (0..=0x10_FFFF).contains(&c) {
17            self.printer.print_char(c as u32);
18        }
19        self.printer.print_bytes(s);
20    }
21
22    /// [`print_esc_bytes`](Self::print_esc_bytes) for ASCII names.
23    pub fn print_esc_str(&mut self, s: &str) {
24        self.print_esc_bytes(s.as_bytes());
25    }
26
27    /// Prints a control sequence with a trailing space where TeX adds one
28    /// (`print_cs`).
29    pub fn print_cs(&mut self, p: CsId) {
30        if !self.hash.contains(p) {
31            // A corrupt token (§262).
32            self.print_esc_str("IMPOSSIBLE.");
33            return;
34        }
35        match self.hash.kind(p).clone() {
36            CsKind::Null => {
37                self.print_esc_str("csname");
38                self.print_esc_str("endcsname");
39                self.printer.print_char(' ' as u32);
40            }
41            CsKind::Single(c) => {
42                self.print_esc_bytes(&crate::utf8x::encode(c));
43                if self.eqtb.catcode(c) == 11 {
44                    self.printer.print_char(' ' as u32);
45                }
46            }
47            CsKind::Active(c) => self.printer.print_char(c),
48            CsKind::Undefined => self.print_esc_str("IMPOSSIBLE."),
49            CsKind::Internal => {
50                let name = self.hash.name(p).to_vec();
51                self.printer.print_bytes(&name);
52            }
53            CsKind::Multi | CsKind::Frozen => {
54                let name = self.hash.name(p).to_vec();
55                self.print_esc_bytes(&name);
56                self.printer.print_char(' ' as u32);
57            }
58        }
59    }
60
61    /// Prints a control sequence without a trailing space (`sprint_cs`).
62    pub fn sprint_cs(&mut self, p: CsId) {
63        match self.hash.kind(p).clone() {
64            CsKind::Null => {
65                self.print_esc_str("csname");
66                self.print_esc_str("endcsname");
67            }
68            CsKind::Active(c) => self.printer.print_char(c),
69            CsKind::Undefined => self.print_esc_str("IMPOSSIBLE."),
70            CsKind::Internal => {
71                let name = self.hash.name(p).to_vec();
72                self.printer.print_bytes(&name);
73            }
74            _ => {
75                let name = self.hash.name(p).to_vec();
76                self.print_esc_bytes(&name);
77            }
78        }
79    }
80
81    /// Displays tokens `list[start..end]`, at most about `l` characters
82    /// (`show_token_list`). When pseudo-printing, the position `loc` (if
83    /// given via [`Engine::show_token_list_ctx`]) sets the trick count.
84    pub fn show_token_list(&mut self, list: &[Token], start: usize, end: usize, l: i64) {
85        self.show_token_list_impl(list, start, end, None, l);
86    }
87
88    /// [`show_token_list`](Self::show_token_list) for context display: the
89    /// whole list is shown and the break happens at `loc`.
90    pub fn show_token_list_ctx(&mut self, list: &[Token], loc: usize, l: i64) {
91        self.show_token_list_impl(list, 0, list.len(), Some(loc), l);
92    }
93
94    /// `token_show` for a whole list.
95    pub fn token_show_list(&mut self, list: &[Token], start: usize, end: usize, l: i64) {
96        self.show_token_list(list, start, end, l);
97    }
98
99    fn show_token_list_impl(
100        &mut self,
101        list: &[Token],
102        start: usize,
103        end: usize,
104        magic: Option<usize>,
105        l: i64,
106    ) {
107        let mut match_chr = '#' as u32;
108        let mut n = b'0';
109        self.printer.tally = 0;
110        let mut p = start;
111        while p < end && (self.printer.tally as i64) < l {
112            if magic == Some(p) {
113                self.set_trick_count();
114            }
115            let t = list[p];
116            if t.is_cs() {
117                self.print_cs(t.cs_id());
118            } else {
119                let c = t.chr();
120                match Cmd::from_u8(t.cmd_code() as u8) {
121                    Cmd::LeftBrace
122                    | Cmd::RightBrace
123                    | Cmd::MathShift
124                    | Cmd::TabMark
125                    | Cmd::SupMark
126                    | Cmd::SubMark
127                    | Cmd::Spacer
128                    | Cmd::Letter
129                    | Cmd::OtherChar => self.printer.print_char(c),
130                    Cmd::MacParam => {
131                        self.printer.print_char(c);
132                        if !self.single_mac_param {
133                            self.printer.print_char(c);
134                        }
135                    }
136                    Cmd::CarRet => {
137                        // out_param
138                        self.printer.print_char(match_chr);
139                        if c <= 9 {
140                            self.printer.print_char(c + '0' as u32);
141                        } else {
142                            self.printer.print_char('!' as u32);
143                            return;
144                        }
145                    }
146                    Cmd::ParEnd => {
147                        // match
148                        match_chr = c;
149                        self.printer.print_char(c);
150                        n += 1;
151                        self.printer.print_char(u32::from(n));
152                        if n > b'9' {
153                            return;
154                        }
155                    }
156                    Cmd::Stop => {
157                        // Observed: when the output goes to the log only
158                        // (\tracingrestores and \tracingassigns without
159                        // \tracingonline), LuaTeX does not count the `->`
160                        // against the limit; on the terminal and log it
161                        // does, as in tex.web.
162                        let tally = self.printer.tally;
163                        self.printer.print_str("->");
164                        if self.printer.selector == crate::print::Selector::LogOnly {
165                            self.printer.tally = tally;
166                        }
167                    }
168                    _ => self.print_esc_str("BAD."),
169                }
170            }
171            p += 1;
172        }
173        if magic == Some(p) {
174            self.set_trick_count();
175        }
176        if p < end {
177            self.print_esc_str("ETC.");
178        }
179    }
180
181    /// Prints a symbolic form of a command and modifier (`print_cmd_chr`).
182    pub fn print_cmd_chr(&mut self, cmd: Cmd, chr: u32) {
183        let chr_cmd = |e: &mut Engine, s: &str| {
184            e.printer.print_str(s);
185            e.printer.print_char(chr);
186        };
187        match cmd {
188            Cmd::LeftBrace => chr_cmd(self, "begin-group character "),
189            Cmd::RightBrace => chr_cmd(self, "end-group character "),
190            Cmd::MathShift => chr_cmd(self, "math shift character "),
191            Cmd::MacParam if chr < 0x11_0000 => chr_cmd(self, "macro parameter character "),
192            Cmd::SupMark => chr_cmd(self, "superscript character "),
193            Cmd::SubMark => chr_cmd(self, "subscript character "),
194            Cmd::Endv => self.printer.print_str("end of alignment template"),
195            Cmd::Spacer => chr_cmd(self, "blank space "),
196            Cmd::Letter => chr_cmd(self, "the letter "),
197            Cmd::OtherChar => chr_cmd(self, "the character "),
198            Cmd::TabMark if chr < 0x11_0000 => chr_cmd(self, "alignment tab character "),
199            Cmd::UndefinedCs => self.printer.print_str("undefined"),
200            Cmd::LuaCall => {
201                self.printer.print_str("luacall ");
202                self.printer.print_int(i64::from(chr));
203            }
204            Cmd::LuaExpandableCall => {
205                self.printer.print_str("expandable luacall ");
206                self.printer.print_int(i64::from(chr));
207            }
208            Cmd::Call | Cmd::LongCall | Cmd::OuterCall | Cmd::LongOuterCall => {
209                // Handled by print_macro_kind when the macro is known.
210                let n = cmd as u32 - Cmd::Call as u32;
211                if n & 1 != 0 {
212                    self.print_esc_str("long");
213                }
214                if n & 2 != 0 {
215                    self.print_esc_str("outer");
216                }
217                if n > 0 {
218                    self.printer.print_char(' ' as u32);
219                }
220                self.printer.print_str("macro");
221            }
222            Cmd::EndTemplate => self.print_esc_str("outer endtemplate"),
223            Cmd::CharGiven => {
224                self.print_esc_str("char");
225                self.printer.print_hex(i64::from(chr));
226            }
227            Cmd::MathGiven => {
228                self.print_esc_str("mathchar");
229                // Four "hex digits" of the reported value; the first is not
230                // limited to 15 (observed: `\mathchar"[500`).
231                let v = crate::codes::math_given_report(chr) as u32;
232                self.printer.print_char('"' as u32);
233                for d in [v / 0x1000, (v >> 8) & 0xF, (v >> 4) & 0xF, v & 0xF] {
234                    let c = if d < 10 {
235                        '0' as u32 + d
236                    } else {
237                        'A' as u32 + d - 10
238                    };
239                    self.printer.print_char(c);
240                }
241            }
242            Cmd::XmathGiven => {
243                self.print_esc_str("Umathchar");
244                let code = i64::from(chr);
245                self.printer
246                    .print_hex(i64::from(crate::codes::math_class(code)));
247                self.printer
248                    .print_str(&format!("\"{:02X}", crate::codes::math_fam(code)));
249                self.printer
250                    .print_str(&format!("\"{:06X}", crate::codes::math_char(code)));
251            }
252            Cmd::SetFont => {
253                self.printer.print_str("select font ");
254                self.print_font_name_and_size(chr);
255            }
256            Cmd::AssignInt if chr >= REG_OFFSET => {
257                self.print_esc_str("count");
258                self.printer.print_int(i64::from(chr - REG_OFFSET));
259            }
260            Cmd::AssignAttr => {
261                self.print_esc_str("attribute");
262                self.printer
263                    .print_int(i64::from(chr.saturating_sub(REG_OFFSET)));
264            }
265            Cmd::AssignDimen if chr >= REG_OFFSET => {
266                self.print_esc_str("dimen");
267                self.printer.print_int(i64::from(chr - REG_OFFSET));
268            }
269            Cmd::AssignGlue if chr >= REG_OFFSET => {
270                self.print_esc_str("skip");
271                self.printer.print_int(i64::from(chr - REG_OFFSET));
272            }
273            Cmd::AssignMuGlue if chr >= REG_OFFSET => {
274                self.print_esc_str("muskip");
275                self.printer.print_int(i64::from(chr - REG_OFFSET));
276            }
277            Cmd::AssignToks if chr >= REG_OFFSET => {
278                self.print_esc_str("toks");
279                self.printer.print_int(i64::from(chr - REG_OFFSET));
280            }
281            Cmd::IfTest if chr >= crate::cond::UNLESS_CODE => {
282                self.print_esc_str("unless");
283                self.print_cmd_chr(cmd, chr - crate::cond::UNLESS_CODE);
284            }
285            Cmd::Relax if chr == crate::input::NO_EXPAND_FLAG => {
286                self.print_esc_str("notexpanded:");
287            }
288            _ => match crate::primitives::name_of(cmd, chr) {
289                Some(name) => self.print_esc_str(name),
290                None => {
291                    // TeX's fallback for unknown modifiers.
292                    self.printer.print_str("[unknown command code! (");
293                    self.printer.print_int(i64::from(cmd as u8));
294                    self.printer.print_str(", ");
295                    self.printer.print_int(i64::from(chr));
296                    self.printer.print_str(")]");
297                }
298            },
299        }
300    }
301
302    /// Prints the meaning of the current command (`print_meaning`).
303    pub fn print_meaning(&mut self) {
304        if self.cur_cmd.is_call() {
305            let mac = if self.cur_cs != crate::hash::NO_CS {
306                self.eqtb.meaning(self.cur_cs).mac.clone()
307            } else {
308                None
309            };
310            let n = self.cur_cmd as u32 - Cmd::Call as u32;
311            let protected = mac.as_ref().is_some_and(|m| m.protected);
312            if protected {
313                self.print_esc_str("protected");
314            }
315            if n & 1 != 0 {
316                self.print_esc_str("long");
317            }
318            if n & 2 != 0 {
319                self.print_esc_str("outer");
320            }
321            if n > 0 || protected {
322                self.printer.print_char(' ' as u32);
323            }
324            self.printer.print_str("macro");
325            self.printer.print_char(':' as u32);
326            self.printer.print_ln();
327            if let Some(m) = mac {
328                let toks = m.tokens.clone();
329                self.token_show_list(&toks, 0, toks.len(), 10_000_000);
330            }
331        } else if self.cur_cmd == Cmd::TopBotMark && self.cur_chr < 5 {
332            self.print_cmd_chr(self.cur_cmd, self.cur_chr);
333            self.printer.print_char(':' as u32);
334            self.printer.print_ln();
335            if let Some(list) = self.marks.get(0, self.cur_chr) {
336                self.token_show_list(&list, 0, list.len(), 10_000_000);
337            }
338        } else {
339            self.print_cmd_chr(self.cur_cmd, self.cur_chr);
340        }
341    }
342
343    /// Shows the current command in a diagnostic (`show_cur_cmd_chr`).
344    pub fn show_cur_cmd_chr(&mut self) {
345        self.begin_diagnostic();
346        self.printer.print_nl("{");
347        if self.mode != self.shown_mode {
348            self.print_mode(self.mode);
349            self.printer.print_str(": ");
350            self.shown_mode = self.mode;
351        }
352        self.print_cmd_chr(self.cur_cmd, self.cur_chr);
353        if self.eqtb.int(pg::assign_int::TRACINGIFS) > 0
354            && self.cur_cmd >= Cmd::IfTest
355            && self.cur_cmd <= Cmd::FiOrElse
356        {
357            self.printer.print_str(": ");
358            let (mut n, l) = if self.cur_cmd == Cmd::FiOrElse {
359                self.print_cmd_chr(Cmd::IfTest, self.cond.cur_if);
360                self.printer.print_char(' ' as u32);
361                (0, self.cond.if_line)
362            } else {
363                (1, self.input.line())
364            };
365            n += self.cond.stack.len() as i64;
366            self.printer.print_str("(level ");
367            self.printer.print_int(n);
368            self.printer.print_char(')' as u32);
369            if l != 0 {
370                self.printer.print_str(" entered on line ");
371                self.printer.print_int(l.into());
372            }
373        }
374        self.printer.print_char('}' as u32);
375        self.end_diagnostic(false);
376    }
377}