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};
pub(super) const IFNEXTCHAR: Native = Native {
name: "@ifnextchar",
fingerprints: &[(21, 0x8d84_79e2_4ee9_3867)],
executes: true,
run: run_ifnextchar,
};
pub(super) const PGFUTIL_IFNEXTCHAR: Native = Native {
name: "pgfutil@ifnextchar",
fingerprints: &[(21, 0x9b4a_b55f_6062_9c3a)],
executes: true,
run: run_pgfutil_ifnextchar,
};
pub(super) const PGFKEYS_IFNEXTCHAR: Native = Native {
name: "pgf@keys@utilifnextchar",
fingerprints: &[(21, 0xb459_2e15_daac_6864)],
executes: true,
run: run_pgfkeys_ifnextchar,
};
struct Names {
label: &'static str,
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
}
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,
}
}
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);
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
}