retex-engine 1.3.0

The TeX typesetting engine for retex
Documentation
//! hyperxmp's `\hyxmp@add@to@xml` (hyperxmp.sty 2020/10/12 and later):
//!
//! ```tex
//! \newcommand*{\hyxmp@add@to@xml}[1]{%
//!   \bgroup
//!     \@hyxmp@count=0
//!     \ifhyxmp@unicodetex \@tempcntb=65536% \else \@tempcntb=256% \fi
//!     \loop
//!       \lccode\@hyxmp@count=\@hyxmp@count
//!       \advance\@hyxmp@count by 1
//!       \ifnum\@hyxmp@count<\@tempcntb
//!     \repeat
//!     \lccode`\_=`\ \relax
//!     \lccode`\^^C=`\,\relax
//!     \lccode`\^^U=`\_\relax
//!     \lowercase{\xdef\hyxmp@new@xml{#1}}%
//!     \xdef\hyxmp@xml{\hyxmp@xml\hyxmp@new@xml}%
//!   \egroup
//! }
//! ```
//!
//! The loop makes 65 536 local `\lccode` assignments on every call, which
//! dominates documents that load hyperxmp (acmart). The native
//! implementation opens the group, makes the loop's assignments in one
//! engine operation ([`Engine::define_codes_identity`], with the
//! save-stack semantics of the individual assignments), leaves the values
//! the loop leaves (`\@hyxmp@count` and `\@tempcntb` both at the bound,
//! `\iterate` let to `\relax`), makes the three single assignments, and
//! puts the rest of the body (`\lowercase…\egroup`, with the argument)
//! back into the input for TeX to run with the same `\lccode`s.
//!
//! It runs where main control executes the macro, outside math mode
//! (where `\bgroup` starts a math group), with no `\globaldefs` and no
//! pending `\afterassignment` (which the first assignment would fire), and
//! only if every control sequence the emulated part uses has its usual
//! meaning.

use super::Native;
use crate::Engine;
use crate::cmd::Cmd;
use crate::eqtb::{CodeKind, Loc, Val};
use crate::maincontrol::{Mode, ModeKind};
use crate::primitives_gen::assign_int as ip;
use crate::scan::REG_OFFSET;
use crate::token::{Token, TokenList};

/// `\hyxmp@add@to@xml`.
pub(super) const ADD_TO_XML: Native = Native {
    name: "hyxmp@add@to@xml",
    fingerprints: &[(75, 0x7121_40da_a940_9c09)],
    executes: true,
    run,
};

/// The fingerprints of LaTeX's `\loop`.
const LOOP: &[u64] = &[0x0bef_e01f_8672_876e];

fn run(e: &mut Engine, args: &[TokenList]) -> bool {
    if e.eqtb.int(ip::GLOBALDEFS) != 0
        || e.after_token.0 != 0
        || !matches!(e.mode.abs_kind(), ModeKind::Vertical | ModeKind::Horizontal)
        || e.mode == Mode::None
    {
        return false;
    }
    let cs = |name: &[u8]| e.hash.lookup(name);
    let meaning = |name: &[u8]| cs(name).map(|id| e.eqtb.meaning(id));
    let prim = |name: &[u8], cmd: Cmd, chr: u32| {
        meaning(name).is_some_and(|m| m.cmd == cmd && m.chr == chr && m.mac.is_none())
    };
    let count_reg = |name: &[u8]| {
        meaning(name)
            .filter(|m| m.cmd == Cmd::AssignInt && m.chr >= REG_OFFSET && m.mac.is_none())
            .map(|m| (m.chr - REG_OFFSET) as usize)
    };
    let (Some(count), Some(tempb)) = (count_reg(b"@hyxmp@count"), count_reg(b"@tempcntb")) else {
        return false;
    };
    let unicode = if prim(b"ifhyxmp@unicodetex", Cmd::IfTest, 14) {
        true
    } else if prim(b"ifhyxmp@unicodetex", Cmd::IfTest, 15) {
        false
    } else {
        return false;
    };
    let bgroup_ok = meaning(b"bgroup").is_some_and(|m| m.cmd == Cmd::LeftBrace && m.mac.is_none());
    let loop_ok = meaning(b"loop").is_some_and(|m| {
        matches!(m.cmd, Cmd::Call | Cmd::LongCall)
            && m.mac
                .as_ref()
                .is_some_and(|mac| LOOP.contains(&e.macro_fingerprint(mac)))
    });
    if super::debug()
        && !loop_ok
        && let Some(m) = meaning(b"loop")
        && let Some(mac) = &m.mac
    {
        eprintln!("[native] \\loop: 0x{:016x}", e.macro_fingerprint(mac));
    }
    if count == tempb
        || !bgroup_ok
        || !loop_ok
        || !prim(b"repeat", Cmd::FiOrElse, 2)
        || !prim(b"lccode", Cmd::DefCharCode, 1)
        || !prim(b"advance", Cmd::Advance, 0)
        || !prim(b"ifnum", Cmd::IfTest, 2)
        || !prim(b"relax", Cmd::Relax, 1_114_112)
    {
        if super::debug() {
            for n in ["bgroup", "repeat", "lccode", "advance", "ifnum", "relax"] {
                if let Some(m) = meaning(n.as_bytes()) {
                    eprintln!("[native]   \\{n}: {:?} {}", m.cmd, m.chr);
                }
            }
            eprintln!("[native] hyxmp@add@to@xml declines (meanings)");
        }
        return false;
    }
    let (Some(iterate), Some(relax), Some(this)) =
        (cs(b"iterate"), cs(b"relax"), cs(b"hyxmp@add@to@xml"))
    else {
        return false;
    };
    // The rest of the body, from `\lowercase`, with the argument.
    let Some(body) = e.eqtb.meaning(this).mac.clone() else {
        return false;
    };
    let Some(lower) = cs(b"lowercase").map(Token::cs) else {
        return false;
    };
    let Some(start) = body.tokens.iter().position(|&t| t == lower) else {
        return false;
    };
    let mut tail = Vec::with_capacity(body.tokens.len() - start + args[0].len());
    for &t in &body.tokens[start..] {
        if t == Token::out_param(1) {
            tail.extend_from_slice(&args[0]);
        } else {
            tail.push(t);
        }
    }
    // Commit.
    let upto: u32 = if unicode { 65536 } else { 256 };
    e.new_save_level(crate::save::GroupCode::Simple);
    e.reset_local_counts(true);
    e.define_codes_identity(CodeKind::Lc, upto);
    e.eq_define(Loc::Count(count as u32), Val::Int(i64::from(upto)));
    e.eq_define(Loc::Count(tempb as u32), Val::Int(i64::from(upto)));
    let relax_m = e.eqtb.meaning(relax).clone();
    e.eq_define(Loc::Cs(iterate), Val::Meaning(relax_m));
    for (c, v) in [
        (u32::from(b'_'), 32),
        (3, i64::from(b',')),
        (21, i64::from(b'_')),
    ] {
        e.eq_define(Loc::Code(CodeKind::Lc, c), Val::Int(v));
    }
    e.native_insert(tail);
    true
}