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 => self.printer.print_str("->"),
157                    _ => self.print_esc_str("BAD."),
158                }
159            }
160            p += 1;
161        }
162        if magic == Some(p) {
163            self.set_trick_count();
164        }
165        if p < end {
166            self.print_esc_str("ETC.");
167        }
168    }
169
170    /// Prints a symbolic form of a command and modifier (`print_cmd_chr`).
171    pub fn print_cmd_chr(&mut self, cmd: Cmd, chr: u32) {
172        let chr_cmd = |e: &mut Engine, s: &str| {
173            e.printer.print_str(s);
174            e.printer.print_char(chr);
175        };
176        match cmd {
177            Cmd::LeftBrace => chr_cmd(self, "begin-group character "),
178            Cmd::RightBrace => chr_cmd(self, "end-group character "),
179            Cmd::MathShift => chr_cmd(self, "math shift character "),
180            Cmd::MacParam => chr_cmd(self, "macro parameter character "),
181            Cmd::SupMark => chr_cmd(self, "superscript character "),
182            Cmd::SubMark => chr_cmd(self, "subscript character "),
183            Cmd::Endv => self.printer.print_str("end of alignment template"),
184            Cmd::Spacer => chr_cmd(self, "blank space "),
185            Cmd::Letter => chr_cmd(self, "the letter "),
186            Cmd::OtherChar => chr_cmd(self, "the character "),
187            Cmd::TabMark if chr < 0x11_0000 => chr_cmd(self, "alignment tab character "),
188            Cmd::UndefinedCs => self.printer.print_str("undefined"),
189            Cmd::LuaCall => {
190                self.printer.print_str("luacall ");
191                self.printer.print_int(i64::from(chr));
192            }
193            Cmd::LuaExpandableCall => {
194                self.printer.print_str("expandable luacall ");
195                self.printer.print_int(i64::from(chr));
196            }
197            Cmd::Call | Cmd::LongCall | Cmd::OuterCall | Cmd::LongOuterCall => {
198                // Handled by print_macro_kind when the macro is known.
199                let n = cmd as u32 - Cmd::Call as u32;
200                if n & 1 != 0 {
201                    self.print_esc_str("long");
202                }
203                if n & 2 != 0 {
204                    self.print_esc_str("outer");
205                }
206                if n > 0 {
207                    self.printer.print_char(' ' as u32);
208                }
209                self.printer.print_str("macro");
210            }
211            Cmd::EndTemplate => self.print_esc_str("outer endtemplate"),
212            Cmd::CharGiven => {
213                self.print_esc_str("char");
214                self.printer.print_hex(i64::from(chr));
215            }
216            Cmd::MathGiven => {
217                self.print_esc_str("mathchar");
218                // Four "hex digits" of the reported value; the first is not
219                // limited to 15 (observed: `\mathchar"[500`).
220                let v = crate::codes::math_given_report(chr) as u32;
221                self.printer.print_char('"' as u32);
222                for d in [v / 0x1000, (v >> 8) & 0xF, (v >> 4) & 0xF, v & 0xF] {
223                    let c = if d < 10 {
224                        '0' as u32 + d
225                    } else {
226                        'A' as u32 + d - 10
227                    };
228                    self.printer.print_char(c);
229                }
230            }
231            Cmd::XmathGiven => {
232                self.print_esc_str("Umathchar");
233                let code = i64::from(chr);
234                self.printer
235                    .print_hex(i64::from(crate::codes::math_class(code)));
236                self.printer
237                    .print_str(&format!("\"{:02X}", crate::codes::math_fam(code)));
238                self.printer
239                    .print_str(&format!("\"{:06X}", crate::codes::math_char(code)));
240            }
241            Cmd::SetFont => {
242                self.printer.print_str("select font ");
243                self.print_font_name_and_size(chr);
244            }
245            Cmd::AssignInt if chr >= REG_OFFSET => {
246                self.print_esc_str("count");
247                self.printer.print_int(i64::from(chr - REG_OFFSET));
248            }
249            Cmd::AssignAttr => {
250                self.print_esc_str("attribute");
251                self.printer
252                    .print_int(i64::from(chr.saturating_sub(REG_OFFSET)));
253            }
254            Cmd::AssignDimen if chr >= REG_OFFSET => {
255                self.print_esc_str("dimen");
256                self.printer.print_int(i64::from(chr - REG_OFFSET));
257            }
258            Cmd::AssignGlue if chr >= REG_OFFSET => {
259                self.print_esc_str("skip");
260                self.printer.print_int(i64::from(chr - REG_OFFSET));
261            }
262            Cmd::AssignMuGlue if chr >= REG_OFFSET => {
263                self.print_esc_str("muskip");
264                self.printer.print_int(i64::from(chr - REG_OFFSET));
265            }
266            Cmd::AssignToks if chr >= REG_OFFSET => {
267                self.print_esc_str("toks");
268                self.printer.print_int(i64::from(chr - REG_OFFSET));
269            }
270            Cmd::IfTest if chr >= crate::cond::UNLESS_CODE => {
271                self.print_esc_str("unless");
272                self.print_cmd_chr(cmd, chr - crate::cond::UNLESS_CODE);
273            }
274            Cmd::Relax if chr == crate::input::NO_EXPAND_FLAG => {
275                self.print_esc_str("notexpanded:");
276            }
277            _ => match crate::primitives::name_of(cmd, chr) {
278                Some(name) => self.print_esc_str(name),
279                None => {
280                    // TeX's fallback for unknown modifiers.
281                    self.printer.print_str("[unknown command code! (");
282                    self.printer.print_int(i64::from(cmd as u8));
283                    self.printer.print_str(", ");
284                    self.printer.print_int(i64::from(chr));
285                    self.printer.print_str(")]");
286                }
287            },
288        }
289    }
290
291    /// Prints the meaning of the current command (`print_meaning`).
292    pub fn print_meaning(&mut self) {
293        if self.cur_cmd.is_call() {
294            let mac = if self.cur_cs != crate::hash::NO_CS {
295                self.eqtb.meaning(self.cur_cs).mac.clone()
296            } else {
297                None
298            };
299            let n = self.cur_cmd as u32 - Cmd::Call as u32;
300            let protected = mac.as_ref().is_some_and(|m| m.protected);
301            if protected {
302                self.print_esc_str("protected");
303            }
304            if n & 1 != 0 {
305                self.print_esc_str("long");
306            }
307            if n & 2 != 0 {
308                self.print_esc_str("outer");
309            }
310            if n > 0 || protected {
311                self.printer.print_char(' ' as u32);
312            }
313            self.printer.print_str("macro");
314            self.printer.print_char(':' as u32);
315            self.printer.print_ln();
316            if let Some(m) = mac {
317                let toks = m.tokens.clone();
318                self.token_show_list(&toks, 0, toks.len(), 10_000_000);
319            }
320        } else if self.cur_cmd == Cmd::TopBotMark && self.cur_chr < 5 {
321            self.print_cmd_chr(self.cur_cmd, self.cur_chr);
322            self.printer.print_char(':' as u32);
323            self.printer.print_ln();
324            if let Some(list) = self.marks.get(0, self.cur_chr) {
325                self.token_show_list(&list, 0, list.len(), 10_000_000);
326            }
327        } else {
328            self.print_cmd_chr(self.cur_cmd, self.cur_chr);
329        }
330    }
331
332    /// Shows the current command in a diagnostic (`show_cur_cmd_chr`).
333    pub fn show_cur_cmd_chr(&mut self) {
334        self.begin_diagnostic();
335        self.printer.print_nl("{");
336        if self.mode != self.shown_mode {
337            self.print_mode(self.mode);
338            self.printer.print_str(": ");
339            self.shown_mode = self.mode;
340        }
341        self.print_cmd_chr(self.cur_cmd, self.cur_chr);
342        if self.eqtb.int(pg::assign_int::TRACINGIFS) > 0
343            && self.cur_cmd >= Cmd::IfTest
344            && self.cur_cmd <= Cmd::FiOrElse
345        {
346            self.printer.print_str(": ");
347            let (mut n, l) = if self.cur_cmd == Cmd::FiOrElse {
348                self.print_cmd_chr(Cmd::IfTest, self.cond.cur_if);
349                self.printer.print_char(' ' as u32);
350                (0, self.cond.if_line)
351            } else {
352                (1, self.input.line())
353            };
354            n += self.cond.stack.len() as i64;
355            self.printer.print_str("(level ");
356            self.printer.print_int(n);
357            self.printer.print_char(')' as u32);
358            if l != 0 {
359                self.printer.print_str(" entered on line ");
360                self.printer.print_int(l.into());
361            }
362        }
363        self.printer.print_char('}' as u32);
364        self.end_diagnostic(false);
365    }
366}