retex-engine 1.3.0

The TeX typesetting engine for retex
Documentation
//! LaTeX kernel macros used everywhere.
//!
//! `\@ifnextchar#1#2#3` (also `\kernel@ifnextchar`, a copy):
//!
//! ```tex
//! \long\def\@ifnextchar#1#2#3{%
//!   \let\reserved@d=#1%
//!   \def\reserved@a{#2}%
//!   \def\reserved@b{#3}%
//!   \futurelet\@let@token\@ifnch}
//! ```
//!
//! `\@ifnch` skips space tokens (`\@xifnch` consumes each and looks
//! again), then lets `\reserved@c` be `\reserved@a` if the next token is
//! `\ifx`-equal to `#1`, else `\reserved@b`, and runs it. The native
//! implementation makes the same five local assignments, consumes the
//! spaces, and puts the chosen branch back into the input. It declines
//! for a `#1` that is not one token (or is a space), branches with macro
//! parameter characters, a next token that is a control sequence `\let`
//! to a space (which `\@xifnch` would not match) or `\outer`, with
//! `\globaldefs` or a pending `\afterassignment`, when the next token is
//! not in a token list on top of the input stack, and when `\@ifnch` or
//! `\@xifnch` have other definitions.
//!
//! pgf has two copies with their own names, `\pgfutil@ifnextchar`
//! (`pgfutil-common.tex`, with `\pgfutil@ifnch`, `\pgfutil@xifnch`,
//! `\pgfutil@reserved@a`…`d`, `\pgfutil@let@token`, `\pgfutil@sptoken`)
//! and `\pgf@keys@utilifnextchar` (`pgfkeys.code.tex`, with
//! `\pgf@keys@util…` names); their `\…ifnch` tests the space first, which
//! makes no difference. The same implementation serves the three.

use std::cell::RefCell;
use std::rc::Rc;

use super::Native;
use super::deps::Deps;
use crate::Engine;
use crate::cmd::Cmd;
use crate::eqtb::{Loc, Meaning, Val};
use crate::primitives_gen::assign_int as ip;
use crate::token::{Macro, Token, TokenList};

/// `\@ifnextchar`.
pub(super) const IFNEXTCHAR: Native = Native {
    name: "@ifnextchar",
    fingerprints: &[(21, 0x8d84_79e2_4ee9_3867)],
    executes: true,
    run: run_ifnextchar,
};

/// `\pgfutil@ifnextchar`.
pub(super) const PGFUTIL_IFNEXTCHAR: Native = Native {
    name: "pgfutil@ifnextchar",
    fingerprints: &[(21, 0x9b4a_b55f_6062_9c3a)],
    executes: true,
    run: run_pgfutil_ifnextchar,
};

/// `\pgf@keys@utilifnextchar`.
pub(super) const PGFKEYS_IFNEXTCHAR: Native = Native {
    name: "pgf@keys@utilifnextchar",
    fingerprints: &[(21, 0xb459_2e15_daac_6864)],
    executes: true,
    run: run_pgfkeys_ifnextchar,
};

/// The names one copy of `\@ifnextchar` uses.
struct Names {
    label: &'static str,
    /// `\reserved@a`, `\reserved@b`, `\reserved@c`, `\reserved@d`,
    /// `\@let@token`, `\@sptoken`.
    names: [&'static [u8]; 6],
}

const NAMES: &[&str] = &["@ifnch", "@xifnch", "@sptoken"];

const KNOWN: &[u64] = &[0x1af6_b74e_04ff_e52b];

const PGFUTIL_NAMES: &[&str] = &["pgfutil@ifnch", "pgfutil@xifnch", "pgfutil@sptoken"];

const PGFUTIL_KNOWN: &[u64] = &[0xaaec_41dd_9d39_7136];

const PGFKEYS_NAMES: &[&str] = &[
    "pgf@keys@utilifnch",
    "pgf@keys@utilxifnch",
    "pgf@keys@utilsptoken",
];

const PGFKEYS_KNOWN: &[u64] = &[0xbc67_625e_c2be_70f2];

thread_local! {
    static DEPS: RefCell<Deps> = RefCell::new(Deps::new("ifnextchar", NAMES, KNOWN));
    static PGFUTIL_DEPS: RefCell<Deps> =
        RefCell::new(Deps::new("pgfutil ifnextchar", PGFUTIL_NAMES, PGFUTIL_KNOWN));
    static PGFKEYS_DEPS: RefCell<Deps> =
        RefCell::new(Deps::new("pgfkeys ifnextchar", PGFKEYS_NAMES, PGFKEYS_KNOWN));
}

fn why(label: &str, reason: &str) -> bool {
    if super::debug() {
        eprintln!("[native] {label} declines ({reason})");
    }
    false
}

/// `\ifx` of two meanings.
pub fn ifx(a: &Meaning, b: &Meaning) -> bool {
    if a.cmd != b.cmd {
        return false;
    }
    if !a.cmd.is_call() {
        return a.chr == b.chr;
    }
    match (&a.mac, &b.mac) {
        (Some(x), Some(y)) => {
            Rc::ptr_eq(x, y) || (x.protected == y.protected && x.tokens == y.tokens)
        }
        _ => false,
    }
}

/// The meaning `\let` or `\futurelet` gives for token `t`.
pub fn token_meaning(e: &Engine, t: Token) -> Meaning {
    if t.is_cs() {
        e.eqtb.meaning(t.cs_id()).clone()
    } else {
        Meaning::prim(t.cmd(), t.chr())
    }
}

fn macro_meaning(body: &[Token]) -> Meaning {
    let mut t = Vec::with_capacity(body.len() + 1);
    t.push(Token::END_MATCH);
    t.extend_from_slice(body);
    Meaning {
        cmd: Cmd::Call,
        chr: 0,
        mac: Some(Rc::new(Macro::new(false, Rc::from(t)))),
    }
}

fn is_param(t: &Token) -> bool {
    !t.is_cs() && (t.cmd_code() == Cmd::MacParam as u32 || t.cmd_code() == Cmd::CarRet as u32)
}

fn run_ifnextchar(e: &mut Engine, args: &[TokenList]) -> bool {
    if !DEPS.with(|d| d.borrow_mut().check(e)) {
        return why("@ifnextchar", "dependencies");
    }
    let n = Names {
        label: "@ifnextchar",
        names: [
            b"reserved@a",
            b"reserved@b",
            b"reserved@c",
            b"reserved@d",
            b"@let@token",
            b"@sptoken",
        ],
    };
    ifnextchar(e, args, &n)
}

fn run_pgfutil_ifnextchar(e: &mut Engine, args: &[TokenList]) -> bool {
    if !PGFUTIL_DEPS.with(|d| d.borrow_mut().check(e)) {
        return why("pgfutil@ifnextchar", "dependencies");
    }
    let n = Names {
        label: "pgfutil@ifnextchar",
        names: [
            b"pgfutil@reserved@a",
            b"pgfutil@reserved@b",
            b"pgfutil@reserved@c",
            b"pgfutil@reserved@d",
            b"pgfutil@let@token",
            b"pgfutil@sptoken",
        ],
    };
    ifnextchar(e, args, &n)
}

fn run_pgfkeys_ifnextchar(e: &mut Engine, args: &[TokenList]) -> bool {
    if !PGFKEYS_DEPS.with(|d| d.borrow_mut().check(e)) {
        return why("pgf@keys@utilifnextchar", "dependencies");
    }
    let n = Names {
        label: "pgf@keys@utilifnextchar",
        names: [
            b"pgf@keys@utilreserved@a",
            b"pgf@keys@utilreserved@b",
            b"pgf@keys@utilreserved@c",
            b"pgf@keys@utilreserved@d",
            b"pgf@keys@utillet@token",
            b"pgf@keys@utilsptoken",
        ],
    };
    ifnextchar(e, args, &n)
}

fn ifnextchar(e: &mut Engine, args: &[TokenList], n: &Names) -> bool {
    let why = |reason: &str| why(n.label, reason);
    if e.eqtb.int(ip::GLOBALDEFS) != 0 || e.after_token.0 != 0 {
        return false;
    }
    let [c] = &args[0][..] else {
        return why("not one token");
    };
    if !c.is_cs() && c.cmd_code() == Cmd::Spacer as u32 {
        return why("space");
    }
    if args[1].iter().any(is_param) || args[2].iter().any(is_param) {
        return why("parameter characters");
    }
    let lookup = |k: usize| e.hash.lookup(n.names[k]);
    let (Some(ra), Some(rb), Some(rc), Some(rd), Some(lt), Some(sp)) = (
        lookup(0),
        lookup(1),
        lookup(2),
        lookup(3),
        lookup(4),
        lookup(5),
    ) else {
        return false;
    };
    let Some(mut f) = e.native_following_subst() else {
        return why("input");
    };
    let space = e.eqtb.meaning(sp).clone();
    let mut i = 0;
    let next = loop {
        let Some(t) = f.get(e, i) else {
            return why("input");
        };
        let m = token_meaning(e, t);
        if ifx(&m, &space) {
            if t.is_cs() || t.chr() != u32::from(b' ') {
                return why("implicit or unusual space");
            }
            i += 1;
            continue;
        }
        if matches!(m.cmd, Cmd::OuterCall | Cmd::LongOuterCall) {
            return why("outer");
        }
        break (t, m);
    };
    if f.count(e, i).is_none() {
        return why("spaces inside a parameter");
    }
    let target = token_meaning(e, *c);
    let take_a = ifx(&next.1, &target);
    // Commit.
    e.native_consume(&f, i);
    e.eq_define(Loc::Cs(rd), Val::Meaning(target));
    let am = macro_meaning(&args[1]);
    let bm = macro_meaning(&args[2]);
    let cm = if take_a { am.clone() } else { bm.clone() };
    e.eq_define(Loc::Cs(ra), Val::Meaning(am));
    e.eq_define(Loc::Cs(rb), Val::Meaning(bm));
    e.eq_define(Loc::Cs(lt), Val::Meaning(next.1));
    e.eq_define(Loc::Cs(rc), Val::Meaning(cm));
    let branch = if take_a { &args[1] } else { &args[2] };
    e.native_insert(branch.to_vec());
    if super::debug() {
        eprintln!("[native] {} native", n.label);
    }
    true
}