retex-engine 0.9.9

The TeX typesetting engine for retex
Documentation
//! Printing control sequences, token lists and meanings (*TeX: The
//! Program* §262–§299).

use crate::Engine;
use crate::cmd::Cmd;
use crate::hash::{CsId, CsKind};
use crate::primitives_gen as pg;
use crate::scan::REG_OFFSET;
use crate::token::Token;

impl Engine {
    /// Prints the escape character (if valid) followed by `s`
    /// (`print_esc`).
    pub fn print_esc_bytes(&mut self, s: &[u8]) {
        let c = self.eqtb.int(pg::assign_int::ESCAPECHAR);
        if (0..=0x10_FFFF).contains(&c) {
            self.printer.print_char(c as u32);
        }
        self.printer.print_bytes(s);
    }

    /// [`print_esc_bytes`](Self::print_esc_bytes) for ASCII names.
    pub fn print_esc_str(&mut self, s: &str) {
        self.print_esc_bytes(s.as_bytes());
    }

    /// Prints a control sequence with a trailing space where TeX adds one
    /// (`print_cs`).
    pub fn print_cs(&mut self, p: CsId) {
        if !self.hash.contains(p) {
            // A corrupt token (§262).
            self.print_esc_str("IMPOSSIBLE.");
            return;
        }
        match self.hash.kind(p).clone() {
            CsKind::Null => {
                self.print_esc_str("csname");
                self.print_esc_str("endcsname");
                self.printer.print_char(' ' as u32);
            }
            CsKind::Single(c) => {
                self.print_esc_bytes(&crate::utf8x::encode(c));
                if self.eqtb.catcode(c) == 11 {
                    self.printer.print_char(' ' as u32);
                }
            }
            CsKind::Active(c) => self.printer.print_char(c),
            CsKind::Undefined => self.print_esc_str("IMPOSSIBLE."),
            CsKind::Internal => {
                let name = self.hash.name(p).to_vec();
                self.printer.print_bytes(&name);
            }
            CsKind::Multi | CsKind::Frozen => {
                let name = self.hash.name(p).to_vec();
                self.print_esc_bytes(&name);
                self.printer.print_char(' ' as u32);
            }
        }
    }

    /// Prints a control sequence without a trailing space (`sprint_cs`).
    pub fn sprint_cs(&mut self, p: CsId) {
        match self.hash.kind(p).clone() {
            CsKind::Null => {
                self.print_esc_str("csname");
                self.print_esc_str("endcsname");
            }
            CsKind::Active(c) => self.printer.print_char(c),
            CsKind::Undefined => self.print_esc_str("IMPOSSIBLE."),
            CsKind::Internal => {
                let name = self.hash.name(p).to_vec();
                self.printer.print_bytes(&name);
            }
            _ => {
                let name = self.hash.name(p).to_vec();
                self.print_esc_bytes(&name);
            }
        }
    }

    /// Displays tokens `list[start..end]`, at most about `l` characters
    /// (`show_token_list`). When pseudo-printing, the position `loc` (if
    /// given via [`Engine::show_token_list_ctx`]) sets the trick count.
    pub fn show_token_list(&mut self, list: &[Token], start: usize, end: usize, l: i64) {
        self.show_token_list_impl(list, start, end, None, l);
    }

    /// [`show_token_list`](Self::show_token_list) for context display: the
    /// whole list is shown and the break happens at `loc`.
    pub fn show_token_list_ctx(&mut self, list: &[Token], loc: usize, l: i64) {
        self.show_token_list_impl(list, 0, list.len(), Some(loc), l);
    }

    /// `token_show` for a whole list.
    pub fn token_show_list(&mut self, list: &[Token], start: usize, end: usize, l: i64) {
        self.show_token_list(list, start, end, l);
    }

    fn show_token_list_impl(
        &mut self,
        list: &[Token],
        start: usize,
        end: usize,
        magic: Option<usize>,
        l: i64,
    ) {
        let mut match_chr = '#' as u32;
        let mut n = b'0';
        self.printer.tally = 0;
        let mut p = start;
        while p < end && (self.printer.tally as i64) < l {
            if magic == Some(p) {
                self.set_trick_count();
            }
            let t = list[p];
            if t.is_cs() {
                self.print_cs(t.cs_id());
            } else {
                let c = t.chr();
                match Cmd::from_u8(t.cmd_code() as u8) {
                    Cmd::LeftBrace
                    | Cmd::RightBrace
                    | Cmd::MathShift
                    | Cmd::TabMark
                    | Cmd::SupMark
                    | Cmd::SubMark
                    | Cmd::Spacer
                    | Cmd::Letter
                    | Cmd::OtherChar => self.printer.print_char(c),
                    Cmd::MacParam => {
                        self.printer.print_char(c);
                        if !self.single_mac_param {
                            self.printer.print_char(c);
                        }
                    }
                    Cmd::CarRet => {
                        // out_param
                        self.printer.print_char(match_chr);
                        if c <= 9 {
                            self.printer.print_char(c + '0' as u32);
                        } else {
                            self.printer.print_char('!' as u32);
                            return;
                        }
                    }
                    Cmd::ParEnd => {
                        // match
                        match_chr = c;
                        self.printer.print_char(c);
                        n += 1;
                        self.printer.print_char(u32::from(n));
                        if n > b'9' {
                            return;
                        }
                    }
                    Cmd::Stop => {
                        // Observed: when the output goes to the log only
                        // (\tracingrestores and \tracingassigns without
                        // \tracingonline), LuaTeX does not count the `->`
                        // against the limit; on the terminal and log it
                        // does, as in tex.web.
                        let tally = self.printer.tally;
                        self.printer.print_str("->");
                        if self.printer.selector == crate::print::Selector::LogOnly {
                            self.printer.tally = tally;
                        }
                    }
                    _ => self.print_esc_str("BAD."),
                }
            }
            p += 1;
        }
        if magic == Some(p) {
            self.set_trick_count();
        }
        if p < end {
            self.print_esc_str("ETC.");
        }
    }

    /// Prints a symbolic form of a command and modifier (`print_cmd_chr`).
    pub fn print_cmd_chr(&mut self, cmd: Cmd, chr: u32) {
        let chr_cmd = |e: &mut Engine, s: &str| {
            e.printer.print_str(s);
            e.printer.print_char(chr);
        };
        match cmd {
            Cmd::LeftBrace => chr_cmd(self, "begin-group character "),
            Cmd::RightBrace => chr_cmd(self, "end-group character "),
            Cmd::MathShift => chr_cmd(self, "math shift character "),
            Cmd::MacParam if chr < 0x11_0000 => chr_cmd(self, "macro parameter character "),
            Cmd::SupMark => chr_cmd(self, "superscript character "),
            Cmd::SubMark => chr_cmd(self, "subscript character "),
            Cmd::Endv => self.printer.print_str("end of alignment template"),
            Cmd::Spacer => chr_cmd(self, "blank space "),
            Cmd::Letter => chr_cmd(self, "the letter "),
            Cmd::OtherChar => chr_cmd(self, "the character "),
            Cmd::TabMark if chr < 0x11_0000 => chr_cmd(self, "alignment tab character "),
            Cmd::UndefinedCs => self.printer.print_str("undefined"),
            Cmd::LuaCall => {
                self.printer.print_str("luacall ");
                self.printer.print_int(i64::from(chr));
            }
            Cmd::LuaExpandableCall => {
                self.printer.print_str("expandable luacall ");
                self.printer.print_int(i64::from(chr));
            }
            Cmd::Call | Cmd::LongCall | Cmd::OuterCall | Cmd::LongOuterCall => {
                // Handled by print_macro_kind when the macro is known.
                let n = cmd as u32 - Cmd::Call as u32;
                if n & 1 != 0 {
                    self.print_esc_str("long");
                }
                if n & 2 != 0 {
                    self.print_esc_str("outer");
                }
                if n > 0 {
                    self.printer.print_char(' ' as u32);
                }
                self.printer.print_str("macro");
            }
            Cmd::EndTemplate => self.print_esc_str("outer endtemplate"),
            Cmd::CharGiven => {
                self.print_esc_str("char");
                self.printer.print_hex(i64::from(chr));
            }
            Cmd::MathGiven => {
                self.print_esc_str("mathchar");
                // Four "hex digits" of the reported value; the first is not
                // limited to 15 (observed: `\mathchar"[500`).
                let v = crate::codes::math_given_report(chr) as u32;
                self.printer.print_char('"' as u32);
                for d in [v / 0x1000, (v >> 8) & 0xF, (v >> 4) & 0xF, v & 0xF] {
                    let c = if d < 10 {
                        '0' as u32 + d
                    } else {
                        'A' as u32 + d - 10
                    };
                    self.printer.print_char(c);
                }
            }
            Cmd::XmathGiven => {
                self.print_esc_str("Umathchar");
                let code = i64::from(chr);
                self.printer
                    .print_hex(i64::from(crate::codes::math_class(code)));
                self.printer
                    .print_str(&format!("\"{:02X}", crate::codes::math_fam(code)));
                self.printer
                    .print_str(&format!("\"{:06X}", crate::codes::math_char(code)));
            }
            Cmd::SetFont => {
                self.printer.print_str("select font ");
                self.print_font_name_and_size(chr);
            }
            Cmd::AssignInt if chr >= REG_OFFSET => {
                self.print_esc_str("count");
                self.printer.print_int(i64::from(chr - REG_OFFSET));
            }
            Cmd::AssignAttr => {
                self.print_esc_str("attribute");
                self.printer
                    .print_int(i64::from(chr.saturating_sub(REG_OFFSET)));
            }
            Cmd::AssignDimen if chr >= REG_OFFSET => {
                self.print_esc_str("dimen");
                self.printer.print_int(i64::from(chr - REG_OFFSET));
            }
            Cmd::AssignGlue if chr >= REG_OFFSET => {
                self.print_esc_str("skip");
                self.printer.print_int(i64::from(chr - REG_OFFSET));
            }
            Cmd::AssignMuGlue if chr >= REG_OFFSET => {
                self.print_esc_str("muskip");
                self.printer.print_int(i64::from(chr - REG_OFFSET));
            }
            Cmd::AssignToks if chr >= REG_OFFSET => {
                self.print_esc_str("toks");
                self.printer.print_int(i64::from(chr - REG_OFFSET));
            }
            Cmd::IfTest if chr >= crate::cond::UNLESS_CODE => {
                self.print_esc_str("unless");
                self.print_cmd_chr(cmd, chr - crate::cond::UNLESS_CODE);
            }
            Cmd::Relax if chr == crate::input::NO_EXPAND_FLAG => {
                self.print_esc_str("notexpanded:");
            }
            _ => match crate::primitives::name_of(cmd, chr) {
                Some(name) => self.print_esc_str(name),
                None => {
                    // TeX's fallback for unknown modifiers.
                    self.printer.print_str("[unknown command code! (");
                    self.printer.print_int(i64::from(cmd as u8));
                    self.printer.print_str(", ");
                    self.printer.print_int(i64::from(chr));
                    self.printer.print_str(")]");
                }
            },
        }
    }

    /// Prints the meaning of the current command (`print_meaning`).
    pub fn print_meaning(&mut self) {
        if self.cur_cmd.is_call() {
            let mac = if self.cur_cs != crate::hash::NO_CS {
                self.eqtb.meaning(self.cur_cs).mac.clone()
            } else {
                None
            };
            let n = self.cur_cmd as u32 - Cmd::Call as u32;
            let protected = mac.as_ref().is_some_and(|m| m.protected);
            if protected {
                self.print_esc_str("protected");
            }
            if n & 1 != 0 {
                self.print_esc_str("long");
            }
            if n & 2 != 0 {
                self.print_esc_str("outer");
            }
            if n > 0 || protected {
                self.printer.print_char(' ' as u32);
            }
            self.printer.print_str("macro");
            self.printer.print_char(':' as u32);
            self.printer.print_ln();
            if let Some(m) = mac {
                let toks = m.tokens.clone();
                self.token_show_list(&toks, 0, toks.len(), 10_000_000);
            }
        } else if self.cur_cmd == Cmd::TopBotMark && self.cur_chr < 5 {
            self.print_cmd_chr(self.cur_cmd, self.cur_chr);
            self.printer.print_char(':' as u32);
            self.printer.print_ln();
            if let Some(list) = self.marks.get(0, self.cur_chr) {
                self.token_show_list(&list, 0, list.len(), 10_000_000);
            }
        } else {
            self.print_cmd_chr(self.cur_cmd, self.cur_chr);
        }
    }

    /// Shows the current command in a diagnostic (`show_cur_cmd_chr`).
    pub fn show_cur_cmd_chr(&mut self) {
        self.begin_diagnostic();
        self.printer.print_nl("{");
        if self.mode != self.shown_mode {
            self.print_mode(self.mode);
            self.printer.print_str(": ");
            self.shown_mode = self.mode;
        }
        self.print_cmd_chr(self.cur_cmd, self.cur_chr);
        if self.eqtb.int(pg::assign_int::TRACINGIFS) > 0
            && self.cur_cmd >= Cmd::IfTest
            && self.cur_cmd <= Cmd::FiOrElse
        {
            self.printer.print_str(": ");
            let (mut n, l) = if self.cur_cmd == Cmd::FiOrElse {
                self.print_cmd_chr(Cmd::IfTest, self.cond.cur_if);
                self.printer.print_char(' ' as u32);
                (0, self.cond.if_line)
            } else {
                (1, self.input.line())
            };
            n += self.cond.stack.len() as i64;
            self.printer.print_str("(level ");
            self.printer.print_int(n);
            self.printer.print_char(')' as u32);
            if l != 0 {
                self.printer.print_str(" entered on line ");
                self.printer.print_int(l.into());
            }
        }
        self.printer.print_char('}' as u32);
        self.end_diagnostic(false);
    }
}