use std::sync::OnceLock;
mod deps;
mod etoolbox;
mod hyperxmp;
mod l3fp;
mod l3keyval;
mod l3regex;
mod l3tl;
mod latex;
mod pgfdim;
mod pgffunc;
mod pgfkeys;
mod pgflength;
mod pgfmath;
mod pgfsys;
mod xcolor;
mod xcolormix;
use crate::Engine;
use crate::primitives_gen::assign_int as ip;
use crate::token::{CS_TOKEN_FLAG, Macro, Token, TokenList};
pub struct Following {
tokens: Vec<Token>,
origin: Vec<(usize, usize)>,
depth: usize,
next: usize,
done: bool,
}
impl Following {
pub fn get(&mut self, e: &Engine, i: usize) -> Option<Token> {
while i >= self.tokens.len() && !self.done {
self.fill(e);
}
self.tokens.get(i).copied()
}
fn fill(&mut self, e: &Engine) {
let stack = &e.input.stack;
let Some(level) = stack.len().checked_sub(self.depth + 1).map(|k| &stack[k]) else {
self.done = true;
return;
};
let crate::input::Level::Tokens(t) = level else {
self.done = true;
return;
};
if matches!(
t.kind,
crate::input::TokKind::VTemplate | crate::input::TokKind::UTemplate
) {
self.done = true;
return;
}
if self.depth == 0 && self.next == 0 && self.tokens.is_empty() {
self.next = t.loc;
}
if self.next >= t.list.len() {
self.depth += 1;
self.next = match stack.len().checked_sub(self.depth + 1).map(|k| &stack[k]) {
Some(crate::input::Level::Tokens(t)) => t.loc,
_ => 0,
};
return;
}
let end = (self.next + 32).min(t.list.len());
for k in self.next..end {
let tok = t.list[k];
if !tok.is_cs() && tok.cmd() == crate::cmd::Cmd::CarRet {
let Some(p) = e
.input
.param_stack
.get(t.param_start + tok.chr() as usize - 1)
else {
self.done = true;
return;
};
self.tokens.extend_from_slice(p);
self.origin
.extend(std::iter::repeat_n((self.depth, k + 1), p.len()));
} else {
self.tokens.push(tok);
self.origin.push((self.depth, k + 1));
}
}
self.next = end;
}
pub fn count(&mut self, e: &Engine, n: usize) -> Option<()> {
if n == 0 {
return Some(());
}
let _ = self.get(e, n);
if n >= self.tokens.len() || self.origin[n] != self.origin[n - 1] {
Some(())
} else {
None
}
}
}
pub struct Native {
pub name: &'static str,
pub fingerprints: &'static [(usize, u64)],
pub executes: bool,
pub run: fn(&mut Engine, &[TokenList]) -> bool,
}
pub static NATIVES: &[Native] = &[
pgfmath::PARSE,
l3fp::PARSE,
l3fp::TO_DECIMAL,
l3keyval::PARSE,
hyperxmp::ADD_TO_XML,
xcolor::COL_RLET,
xcolor::COLORLET,
xcolor::COLOR,
xcolor::MCOLOR,
pgfsys::BP,
pgfsys::LITERAL,
latex::IFNEXTCHAR,
pgfmath::SIN,
pgfmath::COS,
pgfmath::TAN,
pgfmath::ATAN,
pgfmath::ATAN2,
pgfmath::VECLEN,
pgfmath::DIVIDE,
pgfmath::MULTIPLY,
pgfmath::RECIPROCAL,
pgfmath::ADD,
pgfmath::SUBTRACT,
pgfmath::ABS,
pgfmath::DEG,
pgfmath::LN,
pgfmath::SQRT,
pgfmath::ROUND,
pgfmath::MOD,
pgfmath::EXP,
pgfmath::POW,
pgflength::SETLENGTH,
pgflength::ADDTOLENGTH,
etoolbox::IFBLANK,
etoolbox::NOTBLANK,
l3tl::TRIM_SPACES,
l3regex::IF_MATCH,
l3regex::COUNT,
l3regex::EXTRACT_ONCE,
l3regex::EXTRACT_ALL,
l3regex::SPLIT,
l3regex::REPLACE_ONCE,
l3regex::REPLACE_ALL,
latex::PGFUTIL_IFNEXTCHAR,
latex::PGFKEYS_IFNEXTCHAR,
pgfkeys::UNPACK,
pgfkeys::SPDEF,
pgfkeys::ADD_PATH,
pgfkeys::SPLIT_PATH,
pgfkeys::CASE_ONE,
pgfkeys::CASE_TWO,
pgfkeys::CASE_TWO_EXTERN,
pgfkeys::CASE_THREE,
pgfkeys::UNKNOWN,
];
fn lengths() -> &'static [usize] {
static L: OnceLock<Vec<usize>> = OnceLock::new();
L.get_or_init(|| {
if std::env::var_os("RETEX_NO_NATIVE").is_some() {
return Vec::new();
}
let mut v: Vec<usize> = NATIVES
.iter()
.flat_map(|n| n.fingerprints.iter().map(|f| f.0))
.collect();
v.sort_unstable();
v.dedup();
v
})
}
fn debug() -> bool {
static D: OnceLock<bool> = OnceLock::new();
*D.get_or_init(|| std::env::var_os("RETEX_NATIVE_DEBUG").is_some())
}
fn debug_members() -> bool {
static D: OnceLock<bool> = OnceLock::new();
*D.get_or_init(|| std::env::var_os("RETEX_NATIVE_DEBUG").is_some_and(|v| v == "2"))
}
impl Engine {
pub fn macro_fingerprint(&self, mac: &Macro) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
let mut eat = |b: u8| h = (h ^ u64::from(b)).wrapping_mul(0x100_0000_01b3);
eat(u8::from(mac.protected));
for t in mac.tokens.iter() {
if t.0 >= CS_TOKEN_FLAG {
eat(0xFF);
for &b in self.hash.name(crate::hash::CsId(t.0 - CS_TOKEN_FLAG)) {
eat(b);
}
eat(0xFE);
} else {
for b in t.0.to_le_bytes() {
eat(b);
}
}
}
h
}
#[inline]
pub(crate) fn native_of(&self, mac: &Macro, cs: crate::hash::CsId) -> Option<usize> {
match mac.native.get() {
1 => None,
0 => self.native_lookup(mac, cs),
n => Some(n as usize - 2),
}
}
#[cold]
fn native_lookup(&self, mac: &Macro, cs: crate::hash::CsId) -> Option<usize> {
let len = mac.tokens.len();
let named = debug()
.then(|| {
let name = String::from_utf8_lossy(self.hash.name(cs)).into_owned();
NATIVES.iter().any(|n| n.name == name).then_some(name)
})
.flatten();
if lengths().binary_search(&len).is_err() {
if let Some(name) = named {
eprintln!(
"[native] \\{name}: ({len}, 0x{:016x}) no match",
self.macro_fingerprint(mac)
);
}
mac.native.set(1);
return None;
}
let fp = self.macro_fingerprint(mac);
let found = NATIVES
.iter()
.position(|n| n.fingerprints.contains(&(len, fp)));
if let Some(name) = named {
eprintln!(
"[native] \\{name}: ({len}, 0x{fp:016x}) {}",
if found.is_some() {
"matched"
} else {
"no match"
}
);
}
mac.native.set(found.map_or(1, |i| i as u16 + 2));
found
}
pub(crate) fn run_native(&mut self, i: usize, args: &[TokenList], one_step: bool) -> bool {
let n = &NATIVES[i];
if if n.executes {
!self.exec_context
} else {
one_step
} {
if debug() {
eprintln!(
"[native] \\{}: {}",
n.name,
if n.executes {
"not executed by main control"
} else {
"single expansion step"
}
);
}
return false;
}
for p in [
ip::TRACINGMACROS,
ip::TRACINGCOMMANDS,
ip::TRACINGASSIGNS,
ip::TRACINGIFS,
ip::TRACINGGROUPS,
ip::TRACINGRESTORES,
ip::TRACINGNESTING,
] {
if self.eqtb.int(p) > 0 {
if debug() {
eprintln!("[native] \\{}: tracing", n.name);
}
return false;
}
}
(n.run)(self, args)
}
pub fn native_following(&self) -> Option<&[Token]> {
match self.input.stack.last()? {
crate::input::Level::Tokens(t) if t.kind != crate::input::TokKind::VTemplate => {
Some(&t.list[t.loc..])
}
_ => None,
}
}
pub fn native_following_subst(&self) -> Option<Following> {
Some(Following {
tokens: Vec::new(),
origin: Vec::new(),
depth: 0,
next: 0,
done: false,
})
}
pub fn native_consume(&mut self, f: &Following, n: usize) {
if n == 0 {
return;
}
let (depth, loc) = f.origin[n - 1];
for _ in 0..depth {
self.end_token_list();
}
if let Some(crate::input::Level::Tokens(t)) = self.input.stack.last_mut() {
debug_assert!(loc <= t.list.len());
t.loc = loc;
}
}
pub fn native_skip(&mut self, n: usize) {
if let Some(crate::input::Level::Tokens(t)) = self.input.stack.last_mut() {
debug_assert!(t.loc + n <= t.list.len());
t.loc += n;
}
}
pub fn native_insert(&mut self, tokens: Vec<Token>) {
if !tokens.is_empty() {
self.begin_token_list(TokenList::from(tokens), crate::input::TokKind::Inserted);
}
}
}