retex-engine 1.3.0-alpha.2

The TeX typesetting engine for retex
Documentation
//! etoolbox's `\ifblank` and `\notblank`:
//!
//! ```tex
//! \newcommand{\ifblank}[1]{% from expl3
//!   \expandafter\ifx\expandafter\relax\detokenize\expandafter{\@gobble#1?}\relax
//!     \expandafter\@firstoftwo
//!   \else
//!     \expandafter\@secondoftwo
//!   \fi}
//! ```
//!
//! `\@gobble` skips the space tokens in front of its argument, so `#1` is
//! blank exactly when it consists of space tokens (character code 32,
//! category 10) only; the expansion then leaves `\@firstoftwo` (or
//! `\@secondoftwo`), with nothing else changed. The native implementation
//! puts that token back. It runs only when the macros and primitives
//! involved have their usual meanings.

use std::cell::RefCell;

use super::Native;
use super::deps::Deps;
use crate::Engine;
use crate::token::{Token, TokenList};

/// `\ifblank` of etoolbox 2.5.
pub(super) const IFBLANK: Native = Native {
    name: "ifblank",
    fingerprints: &[(20, 0xcd65_4780_b191_5887)],
    executes: false,
    run: run_ifblank,
};

/// `\notblank` of etoolbox 2.5.
pub(super) const NOTBLANK: Native = Native {
    name: "notblank",
    fingerprints: &[(20, 0x973b_8576_eeee_4f45)],
    executes: false,
    run: run_notblank,
};

const NAMES: &[&str] = &[
    "@gobble",
    "@firstoftwo",
    "@secondoftwo",
    "expandafter",
    "ifx",
    "relax",
    "detokenize",
    "else",
    "fi",
];

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

thread_local! {
    static DEPS: RefCell<Deps> = RefCell::new(Deps::new("ifblank", NAMES, KNOWN));
}

fn run_ifblank(e: &mut Engine, args: &[TokenList]) -> bool {
    run(e, args, true)
}

fn run_notblank(e: &mut Engine, args: &[TokenList]) -> bool {
    run(e, args, false)
}

fn run(e: &mut Engine, args: &[TokenList], if_blank: bool) -> bool {
    if !DEPS.with(|d| d.borrow_mut().check(e)) {
        return false;
    }
    let blank = args[0].iter().all(|&t| t == Token::SPACE);
    let name: &[u8] = if blank == if_blank {
        b"@firstoftwo"
    } else {
        b"@secondoftwo"
    };
    let Some(cs) = e.hash.lookup(name) else {
        return false;
    };
    e.native_insert(vec![Token::cs(cs)]);
    true
}