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,
};
const NAMES: &[&str] = &["@ifnch", "@xifnch", "@sptoken"];
const KNOWN: &[u64] = &[0x1af6_b74e_04ff_e52b];
thread_local! {
static DEPS: RefCell<Deps> = RefCell::new(Deps::new("ifnextchar", NAMES, KNOWN));
}
fn why(reason: &str) -> bool {
if super::debug() {
eprintln!("[native] @ifnextchar 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 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");
}
if !DEPS.with(|d| d.borrow_mut().check(e)) {
return why("dependencies");
}
let lookup = |n: &[u8]| e.hash.lookup(n);
let (Some(ra), Some(rb), Some(rc), Some(rd), Some(lt), Some(sp)) = (
lookup(b"reserved@a"),
lookup(b"reserved@b"),
lookup(b"reserved@c"),
lookup(b"reserved@d"),
lookup(b"@let@token"),
lookup(b"@sptoken"),
) 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] @ifnextchar native");
}
true
}