use std::cell::RefCell;
use std::rc::Rc;
use super::Native;
use super::deps::{self, Deps};
use super::pgfdim as d;
use crate::Engine;
use crate::cmd::Cmd;
use crate::eqtb::{Loc, Meaning, Val};
use crate::maincontrol::ModeKind;
use crate::primitives_gen::assign_int as ip;
use crate::scan::REG_OFFSET;
use crate::token::{Macro, Token, TokenList};
pub(super) const BP: Native = Native {
name: "pgf@sys@bp",
fingerprints: &[(42, 0x2126_1980_64d9_9c6f), (34, 0xdde7_4792_5465_d252)],
executes: true,
run: run_bp,
};
pub(super) const LITERAL: Native = Native {
name: "pgfsysprotocol@literal",
fingerprints: &[(10, 0x01eb_1c2f_2bfc_8691)],
executes: true,
run: run_literal,
};
const NAMES: &[&str] = &[
"pgfsysprotocol@literalbuffered",
"pgfsysprotocol@flushcurrentprotocol",
"pgfsysprotocol@invokecurrentprotocol",
"pgfsysprotocol@setcurrentprotocol",
"pgfutil@g@addto@macro",
"pgfutil@toks@",
"pgfutil@empty",
"space",
"pgfsys@invoke",
"Pgf@geT",
"pgf@x",
];
const KNOWN: &[u64] = &[0xf914_c27d_e94d_695a];
const BP_NEW_LEN: usize = 34;
const CORRECT_NAMES: &[&str] = &[
"pgf@sys@bp@correct",
"pgf@sys@bp@correct@",
"number",
"dimexpr",
"numexpr",
"the",
"if",
"else",
"fi",
"relax",
];
const CORRECT_KNOWN: &[u64] = &[0x17df_ed31_7a05_e84b];
thread_local! {
static DEPS: RefCell<Deps> = RefCell::new(Deps::new("pgfsys", NAMES, KNOWN));
static CORRECT_DEPS: RefCell<Deps> =
RefCell::new(Deps::new("pgfsys bp correction", CORRECT_NAMES, CORRECT_KNOWN));
}
fn bp_correct(x: i32) -> i32 {
let n = i64::from(x);
let a = 2 * n + if n < 0 { -1 } else { 1 };
let p = a * 400;
let q = (2 * p.abs() + 803) / (2 * 803);
(if p < 0 { -q } else { q }) as i32
}
fn why(reason: &str) -> bool {
if super::debug() {
eprintln!("[native] pgfsys declines ({reason})");
}
false
}
fn protocol(e: &Engine) -> Option<(crate::hash::CsId, Vec<Token>)> {
if e.eqtb.int(ip::GLOBALDEFS) != 0
|| e.after_token.0 != 0
|| !matches!(e.mode.abs_kind(), ModeKind::Vertical | ModeKind::Horizontal)
{
return None;
}
if !DEPS.with(|d| d.borrow_mut().check(e)) {
why("dependencies");
return None;
}
let cs = e.hash.lookup(b"pgfsysprotocol@currentprotocol")?;
let m = e.eqtb.meaning(cs);
if m.cmd != Cmd::Call || m.mac.as_ref()?.protected {
return None;
}
let body = deps::plain_body(e, cs)?;
Some((cs, body.to_vec()))
}
fn macro_meaning(body: Vec<Token>) -> Meaning {
let mut t = Vec::with_capacity(body.len() + 1);
t.push(Token::END_MATCH);
t.extend(body);
Meaning {
cmd: Cmd::Call,
chr: 0,
mac: Some(Rc::new(Macro::new(false, Rc::from(t)))),
}
}
fn dimension(e: &Engine, arg: &[Token]) -> Option<i32> {
if let [the, reg] = arg
&& the.is_cs()
&& reg.is_cs()
{
let m = e.eqtb.meaning(the.cs_id());
if m.cmd == Cmd::The && m.chr == 0 && m.mac.is_none() {
return dimension(e, &arg[1..]);
}
return None;
}
if let [t] = arg
&& t.is_cs()
{
let m = e.eqtb.meaning(t.cs_id());
if m.cmd == Cmd::AssignDimen && m.chr >= REG_OFFSET && m.mac.is_none() {
return Some(e.eqtb.dimen[(m.chr - REG_OFFSET) as usize].val);
}
return None;
}
let mut s = Vec::with_capacity(arg.len());
for t in arg {
if t.is_cs() || t.chr() > 0x7F {
return None;
}
let c = t.cmd_code();
if c != Cmd::OtherChar as u32 && c != Cmd::Letter as u32 {
return None;
}
s.push(t.chr() as u8);
}
if s.len() < 3 {
return None;
}
let (num, unit) = s.split_at(s.len() - 2);
let unit = d::Unit::from_name(unit[0], unit[1])?;
if num
.iter()
.any(|c| !(c.is_ascii_digit() || *c == b'.' || *c == b'-' || *c == b'+'))
{
return None;
}
if arg[..num.len()]
.iter()
.any(|t| t.cmd_code() != Cmd::OtherChar as u32)
{
return None;
}
d::scan_unit(num, unit).ok()
}
fn run_bp(e: &mut Engine, args: &[TokenList]) -> bool {
let Some((cs, mut body)) = protocol(e) else {
return false;
};
let Some(x) = dimension(e, &args[0]) else {
if super::debug() {
let shown: String = args[0]
.iter()
.map(|t| {
if t.is_cs() {
format!("\\{} ", String::from_utf8_lossy(e.hash.name(t.cs_id())))
} else {
char::from_u32(t.chr()).unwrap_or('?').to_string()
}
})
.collect();
eprintln!("[native] pgfsys bp argument: {shown}");
}
return why("bp argument");
};
let new_formula = e
.hash
.lookup(b"pgf@sys@bp")
.and_then(|cs| e.eqtb.meaning(cs).mac.as_ref().map(|m| m.tokens.len()))
== Some(BP_NEW_LEN);
let x = if new_formula {
if !CORRECT_DEPS.with(|d| d.borrow_mut().check(e)) {
return why("bp correction");
}
bp_correct(x)
} else {
let Ok(x) = d::scan_factor(b"0.99627", x) else {
return why("bp overflow");
};
x
};
body.extend(d::print(x).iter().map(|&c| Token::other(u32::from(c))));
body.push(Token::SPACE);
e.geq_define(Loc::Cs(cs), Val::Meaning(macro_meaning(body)));
if super::debug() {
eprintln!("[native] pgfsys bp native");
}
true
}
fn run_literal(e: &mut Engine, args: &[TokenList]) -> bool {
let Some((cs, mut body)) = protocol(e) else {
return false;
};
let text = &args[0];
if text.iter().any(|t| {
t.is_cs()
|| !(t.cmd_code() == Cmd::OtherChar as u32
|| t.cmd_code() == Cmd::Letter as u32
|| (t.cmd_code() == Cmd::Spacer as u32 && t.chr() == u32::from(b' ')))
}) {
return why("literal text");
}
let lookup = |n: &[u8]| e.hash.lookup(n);
let (Some(temp), Some(buffered), Some(invoke), Some(empty)) = (
lookup(b"pgfsysprotocol@temp"),
lookup(b"ifpgfsysprotocol@buffered"),
lookup(b"pgfsys@invoke"),
lookup(b"pgfutil@empty"),
) else {
return false;
};
let bm = e.eqtb.meaning(buffered);
let buffered = match (bm.cmd, bm.chr, bm.mac.is_none()) {
(Cmd::IfTest, 14, true) => true,
(Cmd::IfTest, 15, true) => false,
_ => return why("buffered flag"),
};
let mut t = vec![Token::LEFT_BRACE];
t.extend_from_slice(text);
t.push(Token::SPACE);
t.push(Token::RIGHT_BRACE);
e.eq_define(Loc::Cs(temp), Val::Meaning(macro_meaning(t)));
body.extend_from_slice(text);
body.push(Token::SPACE);
if buffered {
e.geq_define(Loc::Cs(cs), Val::Meaning(macro_meaning(body)));
} else {
let em = e.eqtb.meaning(empty).clone();
e.geq_define(Loc::Cs(cs), Val::Meaning(em));
let mut ins = Vec::with_capacity(body.len() + 3);
ins.push(Token::cs(invoke));
ins.push(Token::LEFT_BRACE);
ins.extend(body);
ins.push(Token::RIGHT_BRACE);
e.native_insert(ins);
}
if super::debug() {
eprintln!("[native] pgfsys literal native");
}
true
}