use std::cell::RefCell;
use std::rc::Rc;
use super::Native;
use super::pgfdim::{self, Decline, R, Unit};
use super::pgffunc::{self, Fx};
use crate::Engine;
use crate::cmd::Cmd;
use crate::eqtb::{Loc, Meaning, Val};
use crate::hash::CsId;
use crate::input::{Level, TokKind};
use crate::primitives_gen::assign_dimen as dp;
use crate::primitives_gen::assign_int as ip;
use crate::scan::REG_OFFSET;
use crate::token::{Macro, Token, TokenList};
pub const PARSE: Native = Native {
name: "pgfmathparse",
fingerprints: &[(16, 0xe789_8456_7026_5736)],
executes: true,
run: run_parse,
};
const FAST: &[&str] = &[
"pgfmathparse@",
"pgfmath@catcodes",
"pgfmath@quickparsefalse",
"pgfmathunitsdeclaredfalse",
"pgfmathmathunitsdeclaredfalse",
"pgfmath@protected@edef",
"restore@protect",
"pgfmathparse@trynumber@loop",
"pgfmathparse@trynumber@loop@",
"pgf@let@space@token",
"bgroup",
"pgfmath@parse@stop",
"pgfmathparse@trynumber@token",
"pgfmath@token@numeric@.",
"pgfmath@token@numeric@0",
"pgfmath@token@numeric@1",
"pgfmath@token@numeric@2",
"pgfmath@token@numeric@3",
"pgfmath@token@numeric@4",
"pgfmath@token@numeric@5",
"pgfmath@token@numeric@6",
"pgfmath@token@numeric@7",
"pgfmath@token@numeric@8",
"pgfmath@token@numeric@9",
"pgfmathparse@expression@is@number",
"pgfmath@smuggleone",
"iffalse",
"iftrue",
];
const SLOW: &[&str] = &[
"pgfmathparse@@",
"pgfmath@selectfont",
"pgfmath@stack@operation",
"pgfmath@stack@operand",
"pgfmath@stack@empty",
"pgfmath@empty",
"pgfmath@bgroup@strip",
"pgfmath@bgroup@strip@",
"pgfmath@bgroup@@strip",
"pgfmath@bgroup@@@strip",
"pgfmath@bgroup@@@@strip",
"pgfmath@bgroup@pgfmath@",
"pgfmath@parse@@operand",
"pgfmath@parse@ifbgroup",
"pgfmath@parse@@ifbgroup",
"pgfmath@parse@@@ifbgroup",
"pgfmath@parse@@@@ifbgroup",
"pgfmath@sptoken",
"pgfmath@parse@@operand@",
"pgfmath@char@plus",
"pgfmath@char@minus",
"pgfmath@stack@push@operation",
"pgfmath@stack@push",
"pgfmath@operation@(@prefix",
"pgfmath@parse@prefix@operator",
"pgfmath@char@quote",
"pgfmath@char@exclamation",
"pgfmath@parse@number",
"pgfmath@parse@number@",
"pgfmath@parse@@number",
"pgfmath@egroup@token",
"pgfmath@egroup",
"pgfmath@dimen@",
"pgfmath@dimen@@",
"pgfmath@dimen",
"pgfmath@count",
"pgfmathunitsdeclaredtrue",
"pgfmath@tonumber",
"Pgf@geT",
"pgfmath@char@period",
"pgfmath@char@zero",
"pgfmath@parse@operator",
"pgfmath@parse@operator@",
"pgfmath@parse@@operator",
"pgfmath@parse@@operator@",
"pgfmath@parse@@operator@@",
"pgfmath@parse@@@operator",
"pgfmath@operation@process",
"pgfmath@operation@@process",
"pgfmath@text@@",
"pgfmath@char@leftbracket",
"pgfmath@parse@operand",
"pgfmath@operator@@@process",
"pgfmath@stack@pop@operation",
"pgfmath@stack@pop",
"pgfmath@stack@@pop",
"pgfmath@stack@pop@operand",
"pgfmath@stack@push@operand",
"pgfmath@operator@@@@process",
"pgfmath@function@@@process",
"pgfmath@function@@@@process",
"pgfmath@operator@neg",
"pgfmath@operation@neg@precedence",
"pgfmath@operation@neg@arity",
"pgfmath@operation@)@precedence",
"pgfmath@operation@@@precedence",
"pgfmath@operation@)@postfix",
"pgfmath@token@specials@)",
"pgfmath@token@specials@@",
"pgfmathadd@",
"pgfmathsubtract@",
"pgfmathmultiply@",
"pgfmathdivide@",
"pgfmathpow@",
"pgfmathneg@",
"pgfmath@startgroup@",
"pgfmathreciprocal@",
"pgfmathreciprocal@@",
"pgfmathdivide@@",
"pgfmath@divide@periodtrue",
"pgfmath@divide@periodfalse",
"pgfmath@small@number",
"pgfmathdivide@advance@last@digit",
"pgfmath@tonumber@notrailingzero",
"Pgf@geT@NO@TRAILING@ZERO",
"pgfmathdivide@dimenbyten",
"pgfmathdivide@@dimenbyten",
"pgfmath@returnone",
"pgfmath@x",
"pgfmath@y",
"pgfmath@xa",
"pgfmath@xb",
"pgfmath@ya",
"c@pgfmath@counta",
"c@pgfmath@countb",
"pgfmath@tempcnta",
"pgfmath@tempcntb",
"pgfmathloop",
"repeatpgfmathloop",
"pgfmath@parse@end",
];
const OPS: &[&str] = &["+", "-", "*", "/", "^", "("];
const FONT: &[&str] = &[
"selectfont ",
"curr@fontshape",
"pickup@font",
"UseHook",
"hook_use:n",
"__hook selectfont",
"__hook_toplevel selectfont",
"__hook_next selectfont",
"use_i:nn",
"use_none:n",
"if_cs_exist:w",
"cs:w",
"cs_end:",
"fi:",
"@forced@seriesfalse",
];
const FAST_OK: &[u64] = &[0x186b_fc2b_dc27_6b81];
const SLOW_OK: &[u64] = &[0x5ad4_8ed5_2d53_a3d7, 0xfd22_31f2_bcf9_26d5, SLOW_TT];
const SLOW_TT: u64 = 0x4227_0a80_dd1b_035d;
const FONT_OK: &[u64] = &[
0x3c4a_a4cc_f0f9_75b2,
0x983c_00c6_ea54_f492,
0xfdd5_2b7b_4133_39d1,
0xb9c7_595f_0ffb_5d7a,
FONT_RAGGED[0],
FONT_RAGGED[1],
FONT_RAGGED[2],
];
const FONT_RAGGED: [u64; 3] = [
0x823c_261e_cf2b_cd21,
0x5a36_bba2_7ef8_3d1a,
0x05df_2c64_e5d1_42e9,
];
const FUNC_NAMES: &[&str] = &[
"pgfmathsin@",
"pgfmathcos@",
"pgfmathtan@",
"pgfmathatan@",
"pgfmathatan2@",
"pgfmathasin@",
"pgfmathacos@",
"pgfmathveclen@",
"pgfmathsqrt@",
"pgfmathabs@",
"pgfmathmin@",
"pgfmathmax@",
"pgfmathmin@@",
"pgfmathmin@@@",
"pgfmathmax@@",
"pgfmathmax@@@",
"pgfmathmod@",
"pgfmathround@",
"pgfmathfloor@",
"pgfmathfloor@collect@remainder",
"pgfmathceil@",
"pgfmathexp@",
"pgfmathln@",
"pgfmathdeg@",
"pgfmathrad@",
"pgfmathpi@",
"pgfmathe@",
"pgfmathrnd@",
"pgfmathrand@",
"pgfmathrnd@@",
"pgfmathrand@@",
"pgfmathgeneratepseudorandomnumber",
"pgfmath@rnd@m",
"pgfmath@rnd@a",
"pgfmath@rnd@r",
"pgfmath@rnd@q",
"pgfmathgreater@",
"pgfmathless@",
"pgfmathequal@",
"pgfmathnotequal@",
"pgfmathnotless@",
"pgfmathnotgreater@",
"pgfmathand@",
"pgfmathor@",
"pgfmathnot@",
"pgfmathifthenelse@",
"pgfmathfactorial@",
"pgfmathtrue@",
"pgfmathfalse@",
"pgfmath@scale@rad@to@deg",
"pgfmath@scale@deg@to@rad",
"pgfmath@gobbletilpgfmath@",
"pgfmath@multiply@thousand",
"pgfmath@table@lookup",
"pgfmath@table@lookup@",
"pgfmath@sqrt@",
"pgfmath@@sqrt@",
"pgfmath@@@sqrt@",
"pgfmath@sqrt@end",
"pgfmath@collect@",
"pgfmath@@collect@",
"pgfmath@startgroup@",
"pgfmath@parse@function",
"pgfmath@parse@@function",
"pgfmath@parse@@@function",
"pgfmath@parse@@@@function",
"pgfmath@parse@function@",
"pgfmath@parse@prefix@operator",
"pgfmath@parse@exponent",
"pgfmath@parse@@exponent",
"pgfmath@in@",
"pgfmath@gobbleone",
"pgfmath@xc",
"pgfmath@yc",
"pgfmath@yb",
"c@pgfmath@countc",
"c@pgfmath@countd",
"pgfmath@operator@@@process",
"pgfmath@function@@@process",
"pgfmath@function@@@@process",
"pgfmath@operator@@@@process",
];
const FUNC_OK: &[u64] = &[0xc848_9265_c7cf_d72c];
const SCI_NAMES: &[&str] = &[
"pgfmathscientific",
"pgfmath@scientific@@",
"pgfmath@scientific@@@",
];
const SCI_OK: &[u64] = &[0x2735_e230_f667_8eb9];
fn trig_format(e: &Engine) -> Option<bool> {
let cs = e.hash.lookup(b"pgfmath@trig@format@choice")?;
match plain_body(e, cs)? {
[t] if !t.is_cs() && t.chr() == u32::from(b'0') => Some(false),
[t] if !t.is_cs() && t.chr() == u32::from(b'1') => Some(true),
_ => None,
}
}
fn value_tokens(v: &[u8]) -> Vec<Token> {
v.iter()
.map(|&c| match c {
b'{' => Token::LEFT_BRACE,
b'}' => Token::RIGHT_BRACE,
_ => Token::other(u32::from(c)),
})
.collect()
}
const RAGGED_NAMES: &[&str] = &[
"@raggedtwoe@everyselectfont",
"@raggedtwoe@everyselectfont@active",
"if@raggedtwoe@spaceskip",
"spaceskip",
"fontdimen",
"font",
"thr@@",
"tw@",
"z@",
"ifdim",
"else",
"fi",
"relax",
];
const RAGGED_OK: &[u64] = &[
0xcd97_13c2_b85a_57eb,
0x6a4c_7fbe_2f8b_6a95,
0xbd68_af4c_09e0_cf02,
0x2f43_6856_ed75_72ad,
];
#[derive(Clone, Copy)]
#[repr(usize)]
enum M {
Relax,
IfFalse,
IfTrue,
IfFloat,
IfOctal,
PostParse,
Protect,
SizeUpdate,
EncUpdate,
LineSpread,
BaselineStretch,
DelayedAdjust,
Empty,
FontName,
Encoding,
Family,
Series,
Shape,
Size,
PgfX,
PgfmathX,
PgfmathDimen,
PgfmathCount,
UnitsDeclared,
MathUnitsDeclared,
Result,
LoopTemp,
IgnoreSpaces,
Specials,
The,
TtDefault,
}
const MISC: &[&str] = &[
"relax",
"iffalse",
"iftrue",
"ifpgfmathfloat",
"ifpgfmath@octalparsing",
"pgfmathpostparse",
"protect",
"size@update",
"enc@update",
"f@linespread",
"baselinestretch",
"delayed@f@adjustment",
"@empty",
"font@name",
"f@encoding",
"f@family",
"f@series",
"f@shape",
"f@size",
"pgf@x",
"pgfmath@x",
"pgfmath@dimen",
"pgfmath@count",
"ifpgfmathunitsdeclared",
"ifpgfmathmathunitsdeclared",
"pgfmathresult",
"pgfmathloop@temp",
"ignorespaces",
"pgfmath@token@specials@@",
"the",
"ttdefault",
];
fn group_names(g: usize) -> Vec<Vec<u8>> {
let base: &[&str] = match g {
0 => FAST,
1 => SLOW,
_ => FONT,
};
let mut v: Vec<Vec<u8>> = base.iter().map(|s| s.as_bytes().to_vec()).collect();
if g == 1 {
let lasts = ["+", "-", "*", "/", "^", "(", "neg"];
let ops = ["+", "-", "*", "/", "^", ")", "@"];
for o in OPS {
for what in ["prefix", "postfix", "precedence", "arity", "name"] {
v.push(format!("pgfmath@operation@{o}@{what}").into_bytes());
}
v.push(format!("pgfmath@operator@{o}").into_bytes());
v.push(format!("pgfmath@token@specials@{o}").into_bytes());
}
for c in [")", "@"] {
v.push(format!("pgfmath@operator@{c}").into_bytes());
}
for l in lasts {
for o in ops {
v.push(format!("pgfmath@token@group@{l}{o}").into_bytes());
}
}
}
v
}
struct Group {
names: Vec<Vec<u8>>,
ids: Vec<CsId>,
snap: Vec<Meaning>,
ok: bool,
fp: u64,
}
struct State {
groups: Vec<Group>,
misc: Vec<CsId>,
ragged: super::deps::Deps,
funcs: super::deps::Deps,
sci: super::deps::Deps,
}
thread_local! {
static STATE: RefCell<Option<State>> = const { RefCell::new(None) };
}
fn absent(m: &Meaning, relax: &Meaning) -> bool {
m.cmd == Cmd::UndefinedCs || (m.cmd == relax.cmd && m.chr == relax.chr && m.mac.is_none())
}
fn is_register(m: &Meaning) -> bool {
matches!(m.cmd, Cmd::AssignDimen | Cmd::AssignInt | Cmd::AssignToks) && m.chr >= REG_OFFSET
}
fn group_fingerprint(e: &Engine, ids: &[CsId], relax: &Meaning) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
let mut eat = |b: u8| h = (h ^ u64::from(b)).wrapping_mul(0x100_0000_01b3);
for &id in ids {
let m = e.eqtb.meaning(id);
if absent(m, relax) {
eat(0xAA);
} else if let Some(mac) = &m.mac {
eat(m.cmd as u8);
for b in e.macro_fingerprint(mac).to_le_bytes() {
eat(b);
}
} else if is_register(m) {
eat(m.cmd as u8);
eat(0xEE);
} else {
eat(m.cmd as u8);
for b in m.chr.to_le_bytes() {
eat(b);
}
}
eat(0x55);
}
h
}
impl State {
fn new(e: &mut Engine) -> State {
let groups = (0..3)
.map(|g| {
let names = group_names(g);
Group {
ids: names.iter().map(|n| e.hash.lookup_or_insert(n)).collect(),
names,
snap: Vec::new(),
ok: false,
fp: 0,
}
})
.collect();
let misc = MISC
.iter()
.map(|n| e.hash.lookup_or_insert(n.as_bytes()))
.collect();
State {
groups,
misc,
ragged: super::deps::Deps::new("pgfmath ragged2e", RAGGED_NAMES, RAGGED_OK),
funcs: super::deps::Deps::new("pgfmath functions", FUNC_NAMES, FUNC_OK),
sci: super::deps::Deps::new("pgfmath scientific", SCI_NAMES, SCI_OK),
}
}
fn check(&mut self, e: &Engine, g: usize) -> bool {
let gr = &mut self.groups[g];
if gr.snap.len() == gr.ids.len()
&& gr
.ids
.iter()
.zip(&gr.snap)
.all(|(&id, m)| super::deps::snap_eq(e.eqtb.meaning(id), m))
{
return gr.ok;
}
let relax = e.eqtb.meaning(self.misc[M::Relax as usize]).clone();
let fp = group_fingerprint(e, &gr.ids, &relax);
let known = match g {
0 => FAST_OK,
1 => SLOW_OK,
_ => FONT_OK,
};
gr.ok = known.contains(&fp);
gr.fp = fp;
if super::debug() {
let changed = gr
.ids
.iter()
.zip(&gr.snap)
.position(|(&id, m)| !super::deps::snap_eq(e.eqtb.meaning(id), m));
if let Some(k) = changed {
let m = e.eqtb.meaning(gr.ids[k]);
let mut body = String::new();
if let Some(mac) = &m.mac {
for t in mac.tokens.iter() {
if t.is_cs() {
body.push('\\');
body.push_str(&String::from_utf8_lossy(e.hash.name(t.cs_id())));
body.push(' ');
} else {
body.push(char::from_u32(t.chr()).unwrap_or('?'));
}
}
}
eprintln!(
"[native] pgfmath: \\{} changed: {:?} {body}",
String::from_utf8_lossy(&gr.names[k]),
m.cmd
);
}
eprintln!(
"[native] pgfmath dependency group {g}: 0x{fp:016x} {}",
if gr.ok { "matched" } else { "no match" }
);
if super::debug_members() {
for (&id, n) in gr.ids.iter().zip(&gr.names) {
eprintln!(
"[native] pgfmath group {g} member \\{}: 0x{:016x}",
String::from_utf8_lossy(n),
group_fingerprint(e, &[id], &relax)
);
}
}
}
gr.snap = gr
.ids
.iter()
.map(|&id| e.eqtb.meaning(id).clone())
.collect();
gr.ok
}
}
struct Arg {
tokens: Vec<Token>,
pops: usize,
loc: usize,
}
fn collect_arg(e: &Engine) -> Option<Arg> {
let stack = &e.input.stack;
let mut tokens = Vec::new();
let mut depth = 0usize;
let mut started = false;
for (pops, level) in stack.iter().rev().enumerate() {
let Level::Tokens(t) = level else {
return None;
};
if matches!(t.kind, TokKind::UTemplate | TokKind::VTemplate) {
return None;
}
let mut loc = t.loc;
while loc < t.list.len() {
let tok = t.list[loc];
loc += 1;
let mut push = |tok: Token, tokens: &mut Vec<Token>| -> Option<bool> {
if tok.is_cs() {
if !started {
return None;
}
tokens.push(tok);
return Some(false);
}
match tok.cmd() {
Cmd::LeftBrace => {
if started {
return None; }
started = true;
depth += 1;
Some(false)
}
Cmd::RightBrace => {
if !started {
return None;
}
depth -= 1;
Some(depth == 0)
}
Cmd::Spacer if !started => Some(false),
_ if !started => None,
_ => {
tokens.push(tok);
Some(false)
}
}
};
let done = if !tok.is_cs() && tok.cmd() == Cmd::CarRet {
let p = e
.input
.param_stack
.get(t.param_start + tok.chr() as usize - 1)?;
let mut done = false;
for &pt in p.iter() {
if done {
return None;
}
done = push(pt, &mut tokens)?;
}
done
} else {
push(tok, &mut tokens)?
};
if done {
return Some(Arg { tokens, pops, loc });
}
}
}
None
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Tk {
Ch(u8, u8),
Space,
Dimen(i32, CsId),
Count(i32, CsId),
End,
}
struct Parser<'a> {
e: &'a Engine,
relax: &'a Meaning,
s: Vec<Tk>,
i: usize,
pending: Option<Tk>,
token: Tk,
next: Tk,
number: Vec<u8>,
octal: bool,
ops: Vec<Vec<u8>>,
vals: Vec<Vec<u8>>,
result: Option<Vec<u8>>,
units: bool,
pow_used: bool,
font: u32,
name: Vec<u8>,
fx: Fx<'a>,
sci_ok: bool,
}
fn is_numeric(c: u8) -> bool {
c == b'.' || c.is_ascii_digit()
}
impl Parser<'_> {
fn fetch(&mut self) -> R<Tk> {
loop {
let t = self.fetch_raw()?;
if t != Tk::Space {
return Ok(t);
}
}
}
fn fetch_raw(&mut self) -> R<Tk> {
if let Some(t) = self.pending.take() {
return Ok(t);
}
let t = *self.s.get(self.i).ok_or(Decline)?;
self.i += 1;
Ok(t)
}
fn peek_raw(&self) -> Option<Tk> {
self.pending.or_else(|| self.s.get(self.i).copied())
}
fn string(&self, t: Tk, out: &mut Vec<u8>) {
match t {
Tk::Ch(c, _) => out.push(c),
Tk::Space => out.push(b' '),
Tk::End => out.push(b'@'),
Tk::Dimen(_, cs) | Tk::Count(_, cs) => {
out.push(b'\\');
out.extend_from_slice(self.e.hash.name(cs));
}
}
}
fn table_cs(&mut self, prefix: &str, mid: &[u8], parts: &[Tk], suffix: &str) -> Option<CsId> {
let mut name = std::mem::take(&mut self.name);
name.clear();
name.extend_from_slice(prefix.as_bytes());
name.extend_from_slice(mid);
for &t in parts {
self.string(t, &mut name);
}
name.extend_from_slice(suffix.as_bytes());
let r = self
.e
.hash
.lookup(&name)
.filter(|&id| !absent(self.e.eqtb.meaning(id), self.relax));
self.name = name;
r
}
fn table(&mut self, prefix: &str, mid: &[u8], parts: &[Tk], suffix: &str) -> bool {
self.table_cs(prefix, mid, parts, suffix).is_some()
}
fn table_text(&mut self, prefix: &str, mid: &[u8], suffix: &str) -> R<Vec<u8>> {
let cs = self.table_cs(prefix, mid, &[], suffix).ok_or(Decline)?;
let m = self.e.eqtb.meaning(cs);
if m.cmd != Cmd::Call {
return Err(Decline);
}
let body = plain_body(self.e, cs).ok_or(Decline)?;
let mut out = Vec::with_capacity(body.len());
for &t in body {
if t.is_cs()
|| t.chr() > 0x7F
|| !(t.cmd_code() == Cmd::OtherChar as u32 || t.cmd_code() == Cmd::Letter as u32)
{
return Err(Decline);
}
out.push(t.chr() as u8);
}
Ok(out)
}
fn op_number(&mut self, name: &[u8], what: &str) -> R<i64> {
let text = self.table_text("pgfmath@operation@", name, what)?;
pgfdim::scan_int(&text)
}
fn op_is(&mut self, t: Tk, what: &str) -> bool {
let Some(cs) = self.table_cs("pgfmath@operation@", b"", &[t], what) else {
return false;
};
let Some(body) = plain_body(self.e, cs) else {
return false;
};
match (t, body) {
(Tk::Ch(c, cat), [b]) => {
!b.is_cs() && b.chr() == u32::from(c) && b.cmd_code() == u32::from(cat)
}
_ => false,
}
}
fn operand(&mut self) -> R<()> {
loop {
let t = self.fetch()?;
match t {
Tk::Ch(b'+', 12) => continue,
Tk::Ch(b'-', 12) => {
self.ops.push(b"neg".to_vec());
continue;
}
_ => {}
}
if self.op_is(t, "@prefix") {
match t {
Tk::Ch(b'!', 12) => self.ops.push(b"not".to_vec()),
Tk::Ch(b'(', 12) => self.ops.push(b"(".to_vec()),
_ => return Err(Decline), }
continue;
}
if self.table("pgfmath@token@functional@", b"", &[t], "") {
return self.function(t);
}
self.number.clear();
self.octal = matches!(t, Tk::Ch(b'0', _));
self.next = t;
return self.parse_number();
}
}
fn function(&mut self, first: Tk) -> R<()> {
let mut name = Vec::new();
self.string(first, &mut name);
let mut first_step = true;
let token = loop {
if first_step {
while self.peek_raw() == Some(Tk::Space) {
self.fetch_raw()?;
}
first_step = false;
}
match self.peek_raw() {
Some(Tk::Space) => break None,
None => return Err(Decline),
Some(_) => {}
}
let t = self.fetch_raw()?;
if self.table("pgfmath@token@functional@", b"", &[t], "")
|| self.table("pgfmath@token@number@", b"", &[t], "")
{
self.string(t, &mut name);
} else {
break Some(t);
}
};
if !self.table("pgfmath@function@", &name, &[], "") {
return Err(Decline); }
let arity = self.op_number(&name, "@arity")?;
if arity == 0 {
let r = self.fx.call(&name, &[]).ok_or(Decline)??;
self.push_val(r);
if let Some(t) = token {
self.pending = Some(t);
}
return self.parse_two_operator();
}
self.ops.push(name);
if let Some(t) = token {
self.pending = Some(t);
}
self.operand()
}
fn push_val(&mut self, v: Vec<u8>) {
self.result = Some(v.clone());
self.vals.push(v);
}
fn parse_number(&mut self) -> R<()> {
loop {
let x = self.fetch()?;
self.token = self.next;
self.next = x;
let token = self.token;
match token {
Tk::Dimen(v, _) | Tk::Count(v, _) => {
if self.table("pgfmath@token@dimexpr@", b"", &[token], "")
|| self.table("pgfmath@token@box@", b"", &[token], "")
{
return Err(Decline);
}
if let Tk::Dimen(..) = token {
self.units = true;
let d = pgfdim::scan_factor(&self.number, v)?;
self.push_val(pgfdim::print(d));
return self.parse_operator();
}
self.number = v.to_string().into_bytes();
if matches!(self.next, Tk::Dimen(..) | Tk::Count(..)) {
continue;
}
let n = self.number.clone();
self.push_val(n);
return self.parse_operator();
}
Tk::Ch(c, _) if is_numeric(c) => {
self.number.push(c);
if c == b'.' {
self.octal = false;
}
}
_ => {
let next = self.next;
if self.table("pgfmath@token@unit@", b"", &[token, next], "") {
let unit = match (token, next) {
(Tk::Ch(a, _), Tk::Ch(b, _)) => match (a, b) {
(b'e', b'm') => Unit::Internal(self.e.quad(self.font)),
(b'e', b'x') => Unit::Internal(self.e.x_height(self.font)),
(b'p', b'x') => Unit::Internal(self.e.eqtb.dimen(dp::PXDIMEN)),
_ => Unit::from_name(a, b).ok_or(Decline)?,
},
_ => return Err(Decline),
};
self.units = true;
let d = pgfdim::scan_unit(&self.number, unit)?;
self.push_val(pgfdim::print(d));
return self.parse_two_operator();
}
if self.table("pgfmath@token@unit@", b"", &[token], "") {
return Err(Decline);
}
if self.table("pgfmath@token@exponent@", b"", &[token], "") {
return self.exponent();
}
let number = std::mem::take(&mut self.number);
let base = self.table("pgfmath@base@", &number, &[token], "");
self.number = number;
if base || self.table("pgfmath@token@mathunit@", b"", &[token, next], "") {
return Err(Decline);
}
if self.number.is_empty() || (self.octal && self.number != b"0") {
return Err(Decline);
}
let n = self.number.clone();
self.push_val(n);
return self.operator();
}
}
}
}
fn exponent(&mut self) -> R<()> {
let mut negative = false;
let mut t = self.next;
loop {
match t {
Tk::Space => {}
Tk::Ch(b'+', 12) => {}
Tk::Ch(b'-', 12) => negative = !negative,
_ => break,
}
t = self.fetch_raw()?;
}
let mut digits = Vec::new();
while let Tk::Ch(c, 12) = t {
if !c.is_ascii_digit() {
break;
}
digits.push(c);
t = self.fetch_raw()?;
}
if digits.is_empty() {
return Err(Decline); }
if t != Tk::Space {
self.pending = Some(t);
}
let mut n = pgfdim::scan_int(&digits)?;
if negative {
n = -n;
}
if !self.sci_ok {
return Err(Decline);
}
let number = std::mem::take(&mut self.number);
let r = self.fx.scientific(&number, n)?;
self.push_val(r);
self.parse_two_operator()
}
fn parse_operator(&mut self) -> R<()> {
let x = self.fetch()?;
self.token = self.next;
self.next = x;
self.operator()
}
fn parse_two_operator(&mut self) -> R<()> {
self.token = self.fetch()?;
self.next = self.fetch()?;
self.operator()
}
fn operator(&mut self) -> R<()> {
let (token, next) = (self.token, self.next);
if self.table("pgfmath@operator@", b"", &[token, next], "") {
let mut op = Vec::new();
self.string(token, &mut op);
self.string(next, &mut op);
self.process(&op)?;
return self.operand();
}
if !self.table("pgfmath@operator@", b"", &[token], "") {
return Err(Decline);
}
let mut op = Vec::new();
self.string(token, &mut op);
self.process(&op)?;
if token == Tk::End {
return Ok(());
}
if self.op_is(token, "@postfix") {
if next == Tk::Ch(b'[', 12) {
return Err(Decline);
}
return self.parse_operator();
}
self.pending = Some(next);
self.operand()
}
fn process(&mut self, op: &[u8]) -> R<()> {
let prec = self.op_number(op, "@precedence")?;
while let Some(top) = self.ops.last().cloned() {
if prec > self.op_number(&top, "@precedence")? {
break;
}
let r = if self.table("pgfmath@operator@", &top, &[], "") {
self.operator_process()?
} else {
self.function_process()?
};
self.push_val(r);
let mut pair = top.clone();
pair.extend_from_slice(op);
if self.table("pgfmath@token@group@", &pair, &[], "") {
break;
}
}
if !self.table("pgfmath@token@specials@", op, &[], "") {
self.ops.push(op.to_vec());
}
Ok(())
}
fn operator_process(&mut self) -> R<Vec<u8>> {
let op = self.ops.pop().ok_or(Decline)?;
let a = self.vals.pop().ok_or(Decline)?;
let arity = self.op_number(&op, "@arity")?;
let name = self.table_text("pgfmath@operation@", &op, "@name")?;
match arity {
1 => self.apply(&name, &[&a]),
2 => {
let b = self.vals.pop().ok_or(Decline)?;
self.apply(&name, &[&b, &a])
}
3 => {
let b = self.vals.pop().ok_or(Decline)?;
let rest = pgffunc::split_args(&a).ok_or(Decline)?;
if rest.len() != 2 {
return Err(Decline);
}
self.apply(&name, &[&b, rest[0], rest[1]])
}
_ => Err(Decline),
}
}
fn function_process(&mut self) -> R<Vec<u8>> {
let f = self.ops.last().cloned().ok_or(Decline)?;
if self.op_number(&f, "@arity")? <= 0 {
return Err(Decline);
}
self.ops.pop();
let o = self.vals.pop().ok_or(Decline)?;
let arity = self.op_number(&f, "@arity")?;
if arity > 1 && arity <= 9 {
let args = pgffunc::split_args(&o).ok_or(Decline)?;
if args.len() as i64 != arity {
return Err(Decline);
}
self.apply(&f, &args)
} else {
self.apply(&f, &[&o])
}
}
fn apply(&mut self, name: &[u8], args: &[&[u8]]) -> R<Vec<u8>> {
match (name, args) {
(b"@startgroup", [a]) => Ok(a.to_vec()),
(b"@collect", [a, b]) => {
let mut r = match strip(a)? {
(s, true) if s == *a => s,
(s, _) => braced(&s),
};
let (s, _) = strip(b)?;
r.extend(braced(&s));
Ok(r)
}
(b"@@collect", [a, b]) => {
let mut r = braced(a);
r.extend(braced(b));
Ok(r)
}
(b"pow", _) => {
self.pow_used = true;
self.fx.call(name, args).ok_or(Decline)?
}
_ => self.fx.call(name, args).ok_or(Decline)?,
}
}
}
fn braced(s: &[u8]) -> Vec<u8> {
let mut r = Vec::with_capacity(s.len() + 2);
r.push(b'{');
r.extend_from_slice(s);
r.push(b'}');
r
}
fn strip(s: &[u8]) -> R<(Vec<u8>, bool)> {
let mut s = s;
loop {
if s.first() != Some(&b'{') {
if s.iter().any(|&c| c == b'{' || c == b'}') {
return Err(Decline);
}
return Ok((s.to_vec(), false));
}
let groups = pgffunc::split_args(s).ok_or(Decline)?;
let whole = groups.len() == 1 && groups[0].len() + 2 == s.len();
if !whole {
if groups.iter().map(|g| g.len() + 2).sum::<usize>() != s.len() {
return Err(Decline);
}
return Ok((s.to_vec(), true));
}
s = &s[1..s.len() - 1];
}
}
fn same(e: &Engine, a: CsId, b: CsId) -> bool {
e.eqtb.meaning(a).same_as(e.eqtb.meaning(b))
}
fn ifx(e: &Engine, a: CsId, b: CsId) -> bool {
let (x, y) = (e.eqtb.meaning(a), e.eqtb.meaning(b));
if x.cmd != y.cmd {
return false;
}
match (&x.mac, &y.mac) {
(Some(p), Some(q)) => Rc::ptr_eq(p, q) || **p == **q,
(None, None) => x.chr == y.chr,
_ => false,
}
}
fn plain_body(e: &Engine, cs: CsId) -> Option<&[Token]> {
let m = e.eqtb.meaning(cs);
if m.cmd != Cmd::Call {
return None;
}
let mac = m.mac.as_ref()?;
if mac.protected || mac.tokens.first() != Some(&Token::END_MATCH) {
return None;
}
Some(&mac.tokens[1..])
}
fn selectfont_font(e: &Engine, misc: &[CsId]) -> Option<(u32, Option<CsId>)> {
let id = |m: M| misc[m as usize];
let relax = id(M::Relax);
if !same(e, id(M::Protect), relax)
|| !same(e, id(M::SizeUpdate), relax)
|| !same(e, id(M::EncUpdate), relax)
|| !ifx(e, id(M::LineSpread), id(M::BaselineStretch))
|| !ifx(e, id(M::DelayedAdjust), id(M::Empty))
{
return None;
}
let mut name: Vec<u8> = Vec::new();
for (k, m) in [M::Encoding, M::Family, M::Series, M::Shape, M::Size]
.into_iter()
.enumerate()
{
if k > 0 {
name.push(b'/');
}
for &t in plain_body(e, id(m))? {
if t.is_cs() {
return None;
}
crate::utf8x::push(&mut name, t.chr());
}
}
let x = e.hash.lookup(&name)?;
let m = e.eqtb.meaning(x);
if m.cmd != Cmd::SetFont {
return None; }
let changed = plain_body(e, id(M::FontName)) != Some(&[Token::cs(x)][..]);
Some((m.chr, changed.then_some(x)))
}
fn expand_arg(e: &Engine, tokens: &[Token], misc: &[CsId]) -> Option<(Vec<Token>, Option<CsId>)> {
let protect = misc[M::Protect as usize];
let mut x = Expander {
e,
the: {
let m = e.eqtb.meaning(misc[M::The as usize]);
(m.cmd, m.chr)
},
input: Reader {
base: tokens,
pos: 0,
stack: Vec::new(),
},
budget: 1000,
last_cs: None,
open: 0,
};
let mut out = Vec::with_capacity(tokens.len());
while let Some(t) = x.input.next() {
if !t.is_cs() {
out.push(t);
continue;
}
let m = e.eqtb.meaning(t.cs_id());
if !m.cmd.is_expandable() {
if t.cs_id() == protect {
return None;
}
out.push(t);
continue;
}
if let Some(mac) = &m.mac
&& mac.protected
{
out.push(t);
continue;
}
let expansion = x.expand_once(t)?;
x.input.push(expansion);
}
if x.open > 0 {
return None;
}
Some((out, x.last_cs))
}
pub(super) fn first_after_expansion(e: &Engine, tokens: &[Token], stop: Token) -> Option<Token> {
let the = e.hash.lookup(b"the").map(|cs| {
let m = e.eqtb.meaning(cs);
(m.cmd, m.chr)
})?;
let mut x = Expander {
e,
the,
input: Reader {
base: tokens,
pos: 0,
stack: Vec::new(),
},
budget: 100,
last_cs: None,
open: 0,
};
let first = x.input.next()?;
if !first.is_cs() || !e.eqtb.meaning(first.cs_id()).cmd.is_expandable() {
return Some(first);
}
let expansion = x.expand_once(first)?;
if expansion.contains(&stop) {
return None;
}
x.input.push(expansion);
x.input.next()
}
struct Reader<'a> {
base: &'a [Token],
pos: usize,
stack: Vec<(TokenList, usize)>,
}
impl Reader<'_> {
fn next(&mut self) -> Option<Token> {
while let Some((list, k)) = self.stack.last_mut() {
if *k < list.len() {
*k += 1;
return Some(list[*k - 1]);
}
self.stack.pop();
}
let t = self.base.get(self.pos).copied()?;
self.pos += 1;
Some(t)
}
fn push(&mut self, tokens: Vec<Token>) {
if !tokens.is_empty() {
self.stack.push((Rc::from(tokens), 0));
}
}
}
struct Expander<'a, 'b> {
e: &'a Engine,
the: (Cmd, u32),
input: Reader<'b>,
budget: u32,
last_cs: Option<CsId>,
open: u32,
}
impl Expander<'_, '_> {
fn expand_once(&mut self, t: Token) -> Option<Vec<Token>> {
self.budget = self.budget.checked_sub(1)?;
if self.input.stack.len() > 100 {
return None;
}
let m = self.e.eqtb.meaning(t.cs_id());
match m.cmd {
Cmd::Call | Cmd::LongCall => {
let long = m.cmd == Cmd::LongCall;
self.macro_call(m.mac.as_ref()?, long)
}
Cmd::CsName if m.chr == 0 => {
let cs = self.cs_name()?;
self.last_cs = Some(cs);
Some(vec![Token::cs(cs)])
}
Cmd::IfTest if m.chr == crate::cond::if_codes::IF_CS => {
let name = self.name_to_endcsname()?;
let cs = self
.e
.hash
.lookup(&name)
.filter(|&cs| self.e.eqtb.meaning(cs).cmd != Cmd::UndefinedCs);
self.last_cs = Some(cs.unwrap_or(crate::hash::UNDEFINED_CS));
if cs.is_some() || self.skip_branch(true)? == crate::cond::ELSE_CODE {
self.open += 1;
}
Some(Vec::new())
}
Cmd::FiOrElse => {
self.open = self.open.checked_sub(1)?;
match m.chr {
crate::cond::FI_CODE => {}
crate::cond::ELSE_CODE if self.skip_branch(false)? == crate::cond::FI_CODE => {}
_ => return None,
}
Some(Vec::new())
}
Cmd::ExpandAfter if m.chr == 0 => {
let first = self.input.next()?;
let second = self.input.next()?;
let expandable =
second.is_cs() && self.e.eqtb.meaning(second.cs_id()).cmd.is_expandable();
let mut out = vec![first];
if expandable {
out.extend(self.expand_once(second)?);
} else {
out.push(second);
}
Some(out)
}
_ if (m.cmd, m.chr) == self.the && m.mac.is_none() => {
let r = self.input.next()?;
if !r.is_cs() {
return None;
}
let rm = self.e.eqtb.meaning(r.cs_id());
if rm.chr < REG_OFFSET || rm.mac.is_some() {
return None;
}
let i = (rm.chr - REG_OFFSET) as usize;
let text = match rm.cmd {
Cmd::AssignDimen => {
let mut v = pgfdim::print(self.e.eqtb.dimen[i].val);
v.extend_from_slice(b"pt");
v
}
Cmd::AssignInt => self.e.eqtb.count[i].val.to_string().into_bytes(),
_ => return None,
};
Some(
text.into_iter()
.map(|c| Token::other(u32::from(c)))
.collect(),
)
}
_ => None,
}
}
fn macro_call(&mut self, mac: &Macro, mac_long: bool) -> Option<Vec<Token>> {
let end = mac.tokens.iter().position(|&t| t == Token::END_MATCH)?;
let mut args: Vec<Vec<Token>> = Vec::with_capacity(end);
for &p in &mac.tokens[..end] {
if p.is_cs() || p.cmd_code() != Cmd::ParEnd as u32 {
return None; }
let mut t = self.input.next()?;
while !t.is_cs() && t.cmd_code() == Cmd::Spacer as u32 {
t = self.input.next()?;
}
if t.is_cs() {
if self.e.hash.name(t.cs_id()) == b"par" && !mac_long {
return None; }
args.push(vec![t]);
continue;
}
match t.cmd_code() {
c if c == Cmd::RightBrace as u32 => return None,
c if c == Cmd::LeftBrace as u32 => {
let mut arg = Vec::new();
let mut depth = 1u32;
loop {
let u = self.input.next()?;
if !u.is_cs() {
if u.cmd_code() == Cmd::LeftBrace as u32 {
depth += 1;
} else if u.cmd_code() == Cmd::RightBrace as u32 {
depth -= 1;
if depth == 0 {
break;
}
}
}
arg.push(u);
}
args.push(arg);
}
_ => args.push(vec![t]),
}
}
let body = &mac.tokens[end + 1..];
let mut out = Vec::with_capacity(body.len());
for &t in body {
if !t.is_cs() && t.cmd_code() == Cmd::CarRet as u32 {
out.extend_from_slice(args.get(t.chr() as usize - 1)?);
} else {
out.push(t);
}
}
Some(out)
}
fn cs_name(&mut self) -> Option<CsId> {
let name = self.name_to_endcsname()?;
let cs = self.e.hash.lookup(&name)?;
(self.e.eqtb.meaning(cs).cmd != Cmd::UndefinedCs).then_some(cs)
}
fn skip_branch(&mut self, to_else: bool) -> Option<u32> {
let mut depth = 0u32;
loop {
let t = self.input.next()?;
if !t.is_cs() {
continue;
}
let m = self.e.eqtb.meaning(t.cs_id());
match m.cmd {
Cmd::IfTest => depth += 1,
Cmd::FiOrElse if depth > 0 => {
if m.chr == crate::cond::FI_CODE {
depth -= 1;
}
}
Cmd::FiOrElse => match m.chr {
crate::cond::FI_CODE => return Some(m.chr),
crate::cond::ELSE_CODE if to_else => return Some(m.chr),
crate::cond::ELSE_CODE => {}
_ => return None,
},
_ => {}
}
}
}
fn name_to_endcsname(&mut self) -> Option<Vec<u8>> {
let mut name = Vec::new();
loop {
let t = self.input.next()?;
if !t.is_cs() {
crate::utf8x::push(&mut name, t.chr());
continue;
}
let m = self.e.eqtb.meaning(t.cs_id());
if m.cmd == Cmd::EndCsName {
break;
}
if !m.cmd.is_expandable() {
return None;
}
let expansion = self.expand_once(t)?;
self.input.push(expansion);
}
Some(name)
}
}
fn lex(e: &Engine, tokens: &[Token], scratch: (u32, u32)) -> Option<Vec<Tk>> {
let mut s = Vec::with_capacity(tokens.len() + 5);
for &t in tokens {
if t.is_cs() {
let cs = t.cs_id();
let m = e.eqtb.meaning(cs);
if m.chr < REG_OFFSET {
return None;
}
let r = m.chr - REG_OFFSET;
match m.cmd {
Cmd::AssignDimen if r != scratch.0 => {
s.push(Tk::Dimen(e.eqtb.dimen[r as usize].val, cs));
}
Cmd::AssignInt if r != scratch.1 => {
s.push(Tk::Count(e.eqtb.count[r as usize].val, cs));
}
_ => return None,
}
continue;
}
let (c, cat) = (t.chr(), t.cmd_code() as u8);
if c > 0x7F {
return None;
}
let c = c as u8;
let ok = match cat {
10 => c == b' ',
11 => c.is_ascii_alphabetic(),
12 => c.is_ascii_alphanumeric() || b".+-*/^()<>=!?:,&|".contains(&c),
7 => c == b'^',
_ => false,
};
if !ok {
return None;
}
s.push(if cat == 10 { Tk::Space } else { Tk::Ch(c, cat) });
}
s.extend([Tk::End; 4]);
Some(s)
}
fn run_parse(e: &mut Engine, _args: &[TokenList]) -> bool {
let Some(arg) = collect_arg(e) else {
return false;
};
parse(e, arg, true, &|_, _| true).is_some()
}
pub(super) type Accept<'a> = &'a dyn Fn(&Engine, &[Token]) -> bool;
fn parse(e: &mut Engine, mut arg: Arg, consume: bool, accept: Accept) -> Option<Vec<Token>> {
if e.eqtb.int(ip::GLOBALDEFS) != 0
|| e.after_token.0 != 0
|| e.eqtb.int(ip::ESCAPECHAR) != i32::from(b'\\')
{
return None;
}
STATE.with(|st| {
let mut st = st.borrow_mut();
if st.is_none() {
*st = Some(State::new(e));
}
let st = st.as_mut().unwrap();
if !st.check(e, 0) {
return None;
}
let misc = st.misc.clone();
let id = |m: M| misc[m as usize];
if !same(e, id(M::IfFloat), id(M::IfFalse)) {
return None;
}
let Some((expanded, arg_cs)) = expand_arg(e, &arg.tokens, &misc) else {
if super::debug() {
eprintln!("[native] pgfmath declines (expansion)");
}
return None;
};
arg.tokens = expanded;
let fast = arg.tokens.iter().all(|t| {
!t.is_cs()
&& matches!(t.cmd_code(), 11 | 12)
&& t.chr() < 0x80
&& is_numeric(t.chr() as u8)
});
let outcome = if fast {
let last = arg.tokens.last().map(|t| {
e.hash
.lookup(format!("pgfmath@token@numeric@{}", t.chr() as u8 as char).as_bytes())
.unwrap_or(crate::hash::NO_CS)
});
Some((arg.tokens.clone(), false, None, None, last, None))
} else {
slow(e, st, &misc, &arg.tokens).map(|(r, units, loops, seed, font_name)| {
let r = value_tokens(&r);
(r, units, loops, seed, Some(id(M::Specials)), font_name)
})
};
if super::debug() {
let mut text = String::new();
for t in &arg.tokens {
if t.is_cs() {
text.push('\\');
text.push_str(&String::from_utf8_lossy(e.hash.name(t.cs_id())));
text.push(' ');
} else {
text.push(char::from_u32(t.chr()).unwrap_or('?'));
}
}
let what = if outcome.is_some() {
"native"
} else {
"declined"
};
eprintln!("[native] pgfmath {what}: {text}");
}
let (result, units, loops, seed, last_cs, font_name) = outcome?;
if !accept(e, &result) {
return None;
}
if consume {
for _ in 0..arg.pops {
e.end_token_list();
}
if let Some(Level::Tokens(t)) = e.input.stack.last_mut() {
t.loc = arg.loc;
}
}
let truth = |b: bool| {
e.eqtb
.meaning(id(if b { M::IfTrue } else { M::IfFalse }))
.clone()
};
let (u, f) = (truth(units), truth(false));
e.geq_define(Loc::Cs(id(M::UnitsDeclared)), Val::Meaning(u));
e.geq_define(Loc::Cs(id(M::MathUnitsDeclared)), Val::Meaning(f));
let digits = |v: i64| {
std::iter::once(Token::END_MATCH)
.chain(v.to_string().bytes().map(|c| Token::other(u32::from(c))))
.collect::<Vec<_>>()
};
if let Some(v) = loops {
e.geq_define(Loc::Cs(id(M::LoopTemp)), macro_val(digits(v)));
}
if let Some(z) = seed
&& let Some(cs) = e.hash.lookup(b"pgfmath@rnd@z")
{
e.geq_define(Loc::Cs(cs), macro_val(digits(z)));
}
if let Some(x) = font_name {
e.geq_define(
Loc::Cs(id(M::FontName)),
macro_val(vec![Token::END_MATCH, Token::cs(x)]),
);
}
let mut body = vec![Token::END_MATCH];
body.extend_from_slice(&result);
e.eq_define(Loc::Cs(id(M::Result)), macro_val(body));
if let Some(cs) = last_cs.or(arg_cs) {
e.last_cs_name = cs;
}
if consume {
e.native_insert(vec![Token::cs(id(M::IgnoreSpaces))]);
}
Some(result)
})
}
pub(super) fn parse_tokens(
e: &mut Engine,
tokens: Vec<Token>,
accept: Accept,
) -> Option<Vec<Token>> {
parse(
e,
Arg {
tokens,
pops: 0,
loc: 0,
},
false,
accept,
)
}
fn why<T>(reason: &str) -> Option<T> {
if super::debug() {
eprintln!("[native] pgfmath declines ({reason})");
}
None
}
fn macro_val(tokens: Vec<Token>) -> Val {
Val::Meaning(Meaning {
cmd: Cmd::Call,
chr: 0,
mac: Some(Rc::new(Macro::new(false, Rc::from(tokens)))),
})
}
type Slow = (Vec<u8>, bool, Option<i64>, Option<i64>, Option<CsId>);
fn slow(e: &Engine, st: &mut State, misc: &[CsId], tokens: &[Token]) -> Option<Slow> {
let id = |m: M| misc[m as usize];
if !st.check(e, 1)
|| !st.check(e, 2)
|| (FONT_RAGGED.contains(&st.groups[2].fp) && !st.ragged.check(e))
|| !same(e, id(M::IfOctal), id(M::IfTrue))
|| !same(e, id(M::PostParse), id(M::Relax))
{
return why("dependencies");
}
let Some((font, font_name)) = selectfont_font(e, misc) else {
return why("selectfont");
};
if st.groups[1].fp == SLOW_TT && e.font(font).ignore_ligatures == 0 {
let family = plain_body(e, id(M::Family))?;
let tt = e.eqtb.meaning(id(M::TtDefault));
let is_tt = tt.cmd == Cmd::LongCall
&& tt.mac.as_ref().is_some_and(|m| {
!m.protected
&& m.tokens.first() == Some(&Token::END_MATCH)
&& &m.tokens[1..] == family
});
if is_tt {
return why("typewriter ligatures");
}
}
if !st.funcs.check(e) {
return why("function dependencies");
}
let Some(rad) = trig_format(e) else {
return why("trig format");
};
let reg = |m: M| e.eqtb.meaning(id(m)).chr.wrapping_sub(REG_OFFSET);
let Some(s) = lex(e, tokens, (reg(M::PgfmathDimen), reg(M::PgfmathCount))) else {
return why("tokens");
};
let relax = e.eqtb.meaning(id(M::Relax)).clone();
let sci_ok = st.sci.check(e);
let mut p = Parser {
e,
relax: &relax,
s,
i: 0,
pending: None,
token: Tk::End,
next: Tk::End,
number: Vec::new(),
octal: false,
ops: Vec::new(),
vals: Vec::new(),
result: None,
units: false,
pow_used: false,
font,
name: Vec::new(),
fx: Fx::new(e, rad),
sci_ok,
};
if let Some(&t) = tokens
.iter()
.find(|t| t.is_cs() || !is_numeric(t.chr() as u8))
{
let first = p.s.iter().copied().find(|&k| match k {
Tk::Ch(c, _) => !is_numeric(c),
_ => true,
});
if !t.is_cs() && t.cmd_code() == 10 {
} else if let Some(k) = first
&& p.table("pgfmath@token@numeric@", b"", &[k], "")
{
return None;
}
}
if p.s
.iter()
.any(|t| matches!(t, Tk::Ch(c, 11) if c.is_ascii_digit()))
{
return None; }
if p.operand().is_err() {
return why("parse");
}
if p.pow_used && !same(e, id(M::PgfX), id(M::PgfmathX)) {
return None;
}
Some((p.result?, p.units, p.fx.loop_temp, p.fx.seed, font_name))
}
#[derive(Clone, Copy)]
enum Reads {
Start(&'static str),
Forbid(&'static [&'static str], &'static [&'static str]),
}
fn run_entry(e: &mut Engine, args: &[TokenList], func: &[u8], reads: Reads) -> bool {
if e.eqtb.int(ip::GLOBALDEFS) != 0 || e.after_token.0 != 0 {
return false;
}
STATE.with(|st| {
let mut st = st.borrow_mut();
if st.is_none() {
*st = Some(State::new(e));
}
let st = st.as_mut().unwrap();
if !st.check(e, 1) || !st.funcs.check(e) {
return why::<()>("function dependencies").is_some();
}
let misc = st.misc.clone();
let id = |m: M| misc[m as usize];
if !same(e, id(M::IfFloat), id(M::IfFalse)) {
return false;
}
let Some(rad) = trig_format(e) else {
return false;
};
if func == b"pow" && !same(e, id(M::PgfX), id(M::PgfmathX)) {
return false;
}
let forbidden = match reads {
Reads::Start(r) => e
.hash
.lookup(r.as_bytes())
.map(|cs| e.eqtb.meaning(cs))
.filter(|m| m.cmd == Cmd::AssignDimen && m.chr >= REG_OFFSET)
.map(|m| m.chr - REG_OFFSET),
Reads::Forbid(..) => None,
};
let reg_of = |n: &str| {
e.hash
.lookup(n.as_bytes())
.map(|cs| e.eqtb.meaning(cs))
.filter(|m| {
matches!(m.cmd, Cmd::AssignDimen | Cmd::AssignInt) && m.chr >= REG_OFFSET
})
.map(|m| (m.cmd, m.chr - REG_OFFSET))
};
let mut texts = Vec::with_capacity(args.len());
for (k, a) in args.iter().enumerate() {
let Some((text, read, used)) = expand_value(e, a, &misc) else {
if super::debug() {
let shown: String = a
.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] pgfmath function argument: {shown}");
}
return why::<()>("function arguments").is_some();
};
match reads {
Reads::Forbid(regs, macros)
if regs
.iter()
.any(|r| reg_of(r).is_some_and(|r| read.contains(&r)))
|| macros.iter().any(|m| {
e.hash
.lookup(m.as_bytes())
.is_some_and(|c| used.contains(&c))
}) =>
{
if super::debug() {
eprintln!(
"[native] pgfmath argument of {} reads what it changes",
String::from_utf8_lossy(func)
);
}
return why::<()>("function arguments").is_some();
}
Reads::Start(_)
if k > 0
&& forbidden.is_some_and(|f| read.contains(&(Cmd::AssignDimen, f))) =>
{
return why::<()>("function arguments").is_some();
}
_ => {}
}
texts.push(text);
}
let refs: Vec<&[u8]> = texts.iter().map(Vec::as_slice).collect();
let mut fx = Fx::new(e, rad);
let Some(Ok(result)) = fx.call(func, &refs) else {
return why::<()>("function").is_some();
};
let (loops, seed, last) = (fx.loop_temp, fx.seed, fx.last_cs);
let digits = |v: i64| {
std::iter::once(Token::END_MATCH)
.chain(v.to_string().bytes().map(|c| Token::other(u32::from(c))))
.collect::<Vec<_>>()
};
if let Some(v) = loops {
e.geq_define(Loc::Cs(id(M::LoopTemp)), macro_val(digits(v)));
}
if let Some(z) = seed
&& let Some(cs) = e.hash.lookup(b"pgfmath@rnd@z")
{
e.geq_define(Loc::Cs(cs), macro_val(digits(z)));
}
let mut body = vec![Token::END_MATCH];
body.extend(value_tokens(&result));
e.eq_define(Loc::Cs(id(M::Result)), macro_val(body));
if let Some(cs) = last {
e.last_cs_name = cs;
}
if super::debug() {
eprintln!("[native] pgfmath function native");
}
true
})
}
type Expanded = (Vec<u8>, Vec<(Cmd, u32)>, Vec<CsId>);
fn expand_value(e: &Engine, tokens: &[Token], misc: &[CsId]) -> Option<Expanded> {
let tonumber = e.hash.lookup(b"pgf@sys@tonumber")?;
let tonumber_m = e.eqtb.meaning(tonumber);
let the = e.eqtb.meaning(misc[M::The as usize]);
let mut out = Vec::new();
let mut read = Vec::new();
let mut used = Vec::new();
let mut stack: Vec<(TokenList, usize)> = vec![(Rc::from(tokens), 0)];
let mut budget = 200;
fn next(stack: &mut Vec<(TokenList, usize)>) -> Option<Token> {
loop {
let (list, k) = stack.last_mut()?;
if *k < list.len() {
*k += 1;
return Some(list[*k - 1]);
}
stack.pop();
}
}
let register = |t: Token| -> Option<(Cmd, u32)> {
if !t.is_cs() {
return None;
}
let m = e.eqtb.meaning(t.cs_id());
(matches!(m.cmd, Cmd::AssignDimen | Cmd::AssignInt)
&& m.chr >= REG_OFFSET
&& m.mac.is_none())
.then(|| (m.cmd, m.chr - REG_OFFSET))
};
while let Some(t) = next(&mut stack) {
if !t.is_cs() {
if t.chr() > 0x7F
|| !(t.cmd_code() == Cmd::OtherChar as u32 || t.cmd_code() == Cmd::Letter as u32)
{
return None;
}
out.push(t.chr() as u8);
continue;
}
budget -= 1;
if budget == 0 {
return None;
}
let m = e.eqtb.meaning(t.cs_id());
if m.same_as(tonumber_m) {
let mut a = next(&mut stack)?;
let braced = !a.is_cs() && a.cmd_code() == Cmd::LeftBrace as u32;
if braced {
a = next(&mut stack)?;
}
let (cmd, r) = register(a)?;
if cmd != Cmd::AssignDimen {
return None;
}
if braced {
let close = next(&mut stack)?;
if close.is_cs() || close.cmd_code() != Cmd::RightBrace as u32 {
return None;
}
}
read.push((cmd, r));
out.extend(pgfdim::print(e.eqtb.dimen[r as usize].val));
continue;
}
if m.cmd == the.cmd && m.chr == the.chr && m.mac.is_none() {
let (cmd, r) = register(next(&mut stack)?)?;
read.push((cmd, r));
if cmd == Cmd::AssignDimen {
out.extend(pgfdim::print(e.eqtb.dimen[r as usize].val));
out.extend_from_slice(b"pt");
} else {
out.extend(e.eqtb.count[r as usize].val.to_string().bytes());
}
continue;
}
if m.cmd == Cmd::Call {
let mac = m.mac.as_ref()?;
if mac.protected || mac.tokens.first() != Some(&Token::END_MATCH) || stack.len() > 50 {
return None;
}
used.push(t.cs_id());
stack.push((Rc::from(&mac.tokens[1..]), 0));
continue;
}
return None;
}
Some((out, read, used))
}
macro_rules! entry {
($konst:ident, $fun:ident, $name:literal, $fp:expr, $func:literal, $reads:expr) => {
fn $fun(e: &mut Engine, args: &[TokenList]) -> bool {
run_entry(e, args, $func, $reads)
}
pub(super) const $konst: Native = Native {
name: $name,
fingerprints: &[$fp],
executes: true,
run: $fun,
};
};
}
const X: Reads = Reads::Start("pgfmath@x");
entry!(
SIN,
run_sin,
"pgfmathsin@",
(188, 0x7f41_5edd_ac18_71f9),
b"sin",
X
);
entry!(
COS,
run_cos,
"pgfmathcos@",
(179, 0xc52a_c096_84e7_c2e9),
b"cos",
X
);
entry!(
TAN,
run_tan,
"pgfmathtan@",
(38, 0xc8cf_062d_88fa_33ee),
b"tan",
X
);
entry!(
ATAN,
run_atan,
"pgfmathatan@",
(131, 0x65a4_adfd_6839_2fe4),
b"atan",
X
);
entry!(
ATAN2,
run_atan2,
"pgfmathatan2@",
(219, 0x63ba_c77d_42d4_6159),
b"atan2",
Reads::Forbid(
&["pgfmath@x", "pgfmath@y"],
&[
"pgfmathresult",
"pgfmath@tempa",
"pgfmath@tempb",
"pgfmath@trig@format@choice",
"pgfmath@trig@format@choice@@",
]
)
);
entry!(
VECLEN,
run_veclen,
"pgfmathveclen@",
(271, 0xc355_4783_250e_c9d5),
b"veclen",
X
);
entry!(
DIVIDE,
run_divide,
"pgfmathdivide@",
(203, 0x034b_477c_9dde_beb3),
b"divide",
X
);
entry!(
MULTIPLY,
run_multiply,
"pgfmathmultiply@",
(26, 0xaacb_c9a3_0388_e69f),
b"multiply",
X
);
entry!(
RECIPROCAL,
run_reciprocal,
"pgfmathreciprocal@",
(112, 0x6baf_94b2_cb7b_2d93),
b"reciprocal",
X
);
entry!(
ADD,
run_add,
"pgfmathadd@",
(24, 0x5505_faf2_0e55_69bf),
b"add",
X
);
entry!(
SUBTRACT,
run_subtract,
"pgfmathsubtract@",
(25, 0xc754_66a2_8a12_a775),
b"subtract",
X
);
entry!(
ABS,
run_abs,
"pgfmathabs@",
(23, 0x7c55_4502_c26b_a71c),
b"abs",
X
);
entry!(
DEG,
run_deg,
"pgfmathdeg@",
(14, 0x2471_36e1_4722_f547),
b"deg",
X
);
entry!(
LN,
run_ln,
"pgfmathln@",
(903, 0xdfd0_811d_160a_71d3),
b"ln",
X
);
entry!(
SQRT,
run_sqrt,
"pgfmathsqrt@",
(191, 0x91da_4d71_d356_f310),
b"sqrt",
X
);
entry!(
ROUND,
run_round,
"pgfmathround@",
(64, 0xe5ce_dc5b_5f09_dc87),
b"round",
X
);
entry!(
MOD,
run_mod,
"pgfmathmod@",
(31, 0x0f9a_74b9_6bce_58fb),
b"mod",
Reads::Start("pgfmath@xa")
);
entry!(
EXP,
run_exp,
"pgfmathexp@",
(105, 0x2d1b_80ed_aab6_1707),
b"exp",
Reads::Forbid(&["pgfmath@xc"], &[])
);
entry!(
POW,
run_pow,
"pgfmathpow@",
(155, 0xe438_b7ff_ef07_21c2),
b"pow",
Reads::Forbid(
&["pgfmath@x", "pgfmath@xa", "pgfmath@xb", "c@pgfmath@counta"],
&["pgfmathresult"]
)
);