use std::cell::RefCell;
use super::Native;
use super::deps::Deps;
use super::latex::{ifx, token_meaning};
use super::pgfdim;
use crate::Engine;
use crate::cmd::Cmd;
use crate::eqtb::{Loc, Meaning, Val};
use crate::primitives_gen::assign_int as ip;
use crate::scan::REG_OFFSET;
use crate::token::{Token, TokenList};
pub(super) const SETLENGTH: Native = Native {
name: "pgfmathsetlength",
fingerprints: &[(41, 0xdfa5_e94d_031f_863c)],
executes: true,
run: run_setlength,
};
pub(super) const ADDTOLENGTH: Native = Native {
name: "pgfmathaddtolength",
fingerprints: &[(44, 0x1b8d_0332_32b4_64e5)],
executes: true,
run: run_addtolength,
};
const NAMES: &[&str] = &[
"pgfmath@onquick",
"pgfmath@@onquick",
"pgfmath@firstoftwo",
"pgfmath@secondoftwo",
"pgfmathparse",
"ifpgfmathmathunitsdeclared",
"iffalse",
"ignorespaces",
"relax",
"futurelet",
"expandafter",
"ifx",
"let",
"else",
"fi",
"advance",
];
const KNOWN: &[u64] = &[0x04eb_cd21_e419_836b];
thread_local! {
static DEPS: RefCell<Deps> = RefCell::new(Deps::new("pgfmathsetlength", NAMES, KNOWN));
}
fn why(reason: &str) -> bool {
if super::debug() {
eprintln!("[native] pgfmathsetlength declines ({reason})");
}
false
}
fn run_setlength(e: &mut Engine, args: &[TokenList]) -> bool {
run(e, args, false)
}
fn run_addtolength(e: &mut Engine, args: &[TokenList]) -> bool {
run(e, args, true)
}
fn quick_token(e: &Engine, arg: &[Token], stop: Token) -> Option<Token> {
super::pgfmath::first_after_expansion(e, arg, stop)
}
fn run(e: &mut Engine, args: &[TokenList], advance: bool) -> bool {
if e.eqtb.int(ip::GLOBALDEFS) != 0 || e.after_token.0 != 0 {
return false;
}
let [reg] = &args[0][..] else {
return why("register");
};
if !reg.is_cs() {
return why("register");
}
let rm = e.eqtb.meaning(reg.cs_id());
if rm.cmd != Cmd::AssignDimen || rm.chr < REG_OFFSET {
return why("register");
}
let n = rm.chr - REG_OFFSET;
if !DEPS.with(|d| d.borrow_mut().check(e)) {
return why("dependencies");
}
let lookup = |n: &[u8]| e.hash.lookup(n);
let (Some(stop), Some(next), Some(second), Some(ignore)) = (
lookup(b"pgfmath@"),
lookup(b"pgfmath@next"),
lookup(b"pgfmath@secondoftwo"),
lookup(b"ignorespaces"),
) else {
return false;
};
let stop = Token::cs(stop);
if args[1].contains(&stop) {
return why("stop token");
}
let Some(t) = quick_token(e, &args[1], stop) else {
return why("first token");
};
let plus = Meaning::prim(Cmd::OtherChar, u32::from(b'+'));
if ifx(&token_meaning(e, t), &plus) {
return why("quick");
}
let value = |e: &Engine, result: &[Token]| -> Option<i32> {
let v = scanned(result)?;
if advance {
pgfdim::adv(e.eqtb.dimen[n as usize].val, v).ok()
} else {
Some(v)
}
};
let Some(result) =
super::pgfmath::parse_tokens(e, args[1].to_vec(), &|e, r| value(e, r).is_some())
else {
return why("parse");
};
let v = value(e, &result).expect("accepted");
let m = e.eqtb.meaning(second).clone();
e.eq_define(Loc::Cs(next), Val::Meaning(m));
e.eq_define(Loc::Dimen(n), Val::Int(i64::from(v)));
e.native_insert(vec![Token::cs(ignore)]);
if super::debug() {
eprintln!("[native] pgfmathsetlength native");
}
true
}
fn scanned(result: &[Token]) -> Option<i32> {
let mut bytes = Vec::with_capacity(result.len());
for t in result {
if t.is_cs() || t.cmd_code() != Cmd::OtherChar as u32 || t.chr() > 0x7f {
return None;
}
bytes.push(t.chr() as u8);
}
pgfdim::scan_pt(&bytes).ok()
}