use std::cell::RefCell;
use std::rc::Rc;
use super::Native;
use super::deps::{self, Deps};
use super::latex::{ifx, token_meaning};
use crate::Engine;
use crate::cmd::Cmd;
use crate::eqtb::{Loc, Meaning, Val};
use crate::hash::{CsId, UNDEFINED_CS};
use crate::primitives_gen::assign_int as ip;
use crate::scan::REG_OFFSET;
use crate::token::{Macro, Token, TokenList};
pub(super) const UNPACK: Native = Native {
name: "pgfkeys@unpack",
fingerprints: &[(110, 0x06cf_faad_e3e9_985f)],
executes: true,
run: run_unpack,
};
pub(super) const SPDEF: Native = Native {
name: "pgfkeys@spdef",
fingerprints: &[(13, 0x7e6f_c7e2_d9c7_179f)],
executes: true,
run: run_spdef,
};
pub(super) const ADD_PATH: Native = Native {
name: "pgfkeys@add@path@as@needed",
fingerprints: &[(9, 0xf48b_e1a9_81a3_a4be)],
executes: true,
run: run_add_path,
};
pub(super) const SPLIT_PATH: Native = Native {
name: "pgfkeys@split@path",
fingerprints: &[(9, 0xf3fc_3dcb_44ea_6702)],
executes: true,
run: run_split_path,
};
pub(super) const CASE_ONE: Native = Native {
name: "pgfkeys@case@one",
fingerprints: &[(48, 0x853a_942e_d2d8_e46f)],
executes: true,
run: run_case_one,
};
pub(super) const CASE_TWO: Native = Native {
name: "pgfkeys@case@two",
fingerprints: &[(11, 0xa59e_9440_3043_5652)],
executes: true,
run: run_case_two,
};
pub(super) const CASE_TWO_EXTERN: Native = Native {
name: "pgfkeys@case@two@extern",
fingerprints: &[(15, 0x5ce1_984c_3449_d0dc)],
executes: true,
run: run_case_two_extern,
};
pub(super) const CASE_THREE: Native = Native {
name: "pgfkeys@case@three",
fingerprints: &[(55, 0x8f18_c389_3eea_3632)],
executes: true,
run: run_case_three,
};
pub(super) const UNKNOWN: Native = Native {
name: "pgfkeys@unknown",
fingerprints: &[(85, 0x1009_de85_84f9_edd7)],
executes: true,
run: run_unknown,
};
const SP_NAMES: &[&str] = &[
"pgfkeys@sp@a",
"pgfkeys@sp@b",
"pgfkeys@sp@c",
"pgfkeys@sptoken",
"pgfkeys@temptoks",
];
const SP_KNOWN: &[u64] = &[0x59cf_ecd0_ee9e_020b];
const PATH_NAMES: &[&str] = &[
"pgfkeys@check@slash",
"pgfkeys@nevermind",
"pgfkeys@addpath",
"pgfkeysaddeddefaultpathtrue",
"pgfkeysaddeddefaultpathfalse",
];
const PATH_KNOWN: &[u64] = &[0x1b95_6438_f637_aae6];
const SPLIT_NAMES: &[&str] = &[
"pgfkeys@splitter",
"pgfkeys@gobbletoslash",
"pgfkeys@remove@slash",
"pgfkeys@pathtoks",
"pgfkeys@empty",
];
const SPLIT_KNOWN: &[u64] = &[0x4898_0410_c139_bdb8];
const KEY_NAMES: &[&str] = &[
"pgfkeysifdefined",
"pgfkeysgetvalue",
"pgfkeyslet",
"pgfkeys@ifcsname",
"pgfkeys@firstoftwo",
"pgfkeys@secondoftwo",
"pgfkeys@split@path",
];
const UNPACK_NAMES: &[&str] = &["pgfkeys@spdef", "pgfkeys@add@path@as@needed"];
const UNPACK_KNOWN: &[u64] = &[0x8c1a_b7c4_72f7_8d77];
const KEY_KNOWN: &[u64] = &[0x5d68_cef1_7c12_10b9];
thread_local! {
static SP_DEPS: RefCell<Deps> = RefCell::new(Deps::new("pgfkeys spdef", SP_NAMES, SP_KNOWN));
static PATH_DEPS: RefCell<Deps> =
RefCell::new(Deps::new("pgfkeys add path", PATH_NAMES, PATH_KNOWN));
static SPLIT_DEPS: RefCell<Deps> =
RefCell::new(Deps::new("pgfkeys split path", SPLIT_NAMES, SPLIT_KNOWN));
static UNPACK_DEPS: RefCell<Deps> =
RefCell::new(Deps::new("pgfkeys unpack", UNPACK_NAMES, UNPACK_KNOWN));
static KEY_DEPS: RefCell<Deps> = RefCell::new(Deps::new("pgfkeys keys", KEY_NAMES, KEY_KNOWN));
}
fn why(what: &str, reason: &str) -> bool {
if super::debug() {
eprintln!("[native] {what} declines ({reason})");
}
false
}
fn plain_state(e: &Engine) -> bool {
e.eqtb.int(ip::GLOBALDEFS) == 0 && e.after_token.0 == 0
}
fn is_left(t: Token) -> bool {
!t.is_cs() && t.cmd_code() == Cmd::LeftBrace as u32
}
fn is_right(t: Token) -> bool {
!t.is_cs() && t.cmd_code() == Cmd::RightBrace as u32
}
fn is_param(t: Token) -> bool {
!t.is_cs() && (t.cmd_code() == Cmd::MacParam as u32 || t.cmd_code() == Cmd::CarRet as u32)
}
pub(super) fn delimited(s: &[Token], from: usize, delim: &[Token]) -> Option<(usize, usize)> {
let mut depth = 0u32;
let mut j = from;
while j < s.len() {
let t = s[j];
if depth == 0 && s[j..].starts_with(delim) {
return Some((j, j + delim.len()));
}
if is_left(t) {
depth += 1;
} else if is_right(t) {
depth = depth.checked_sub(1)?;
}
j += 1;
}
None
}
pub(super) fn strip(arg: &[Token]) -> &[Token] {
if arg.len() < 2 || !is_left(arg[0]) || !is_right(arg[arg.len() - 1]) {
return arg;
}
let mut depth = 0u32;
for (k, &t) in arg.iter().enumerate() {
if is_left(t) {
depth += 1;
} else if is_right(t) {
depth -= 1;
if depth == 0 {
return if k == arg.len() - 1 { &arg[1..k] } else { arg };
}
}
}
arg
}
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 token_list(t: &[Token]) -> TokenList {
if t.is_empty() {
crate::token::empty_list()
} else {
Rc::from(t)
}
}
fn toks_register(e: &Engine, cs: CsId) -> Option<u32> {
let m = e.eqtb.meaning(cs);
(m.cmd == Cmd::AssignToks && m.mac.is_none() && m.chr >= REG_OFFSET).then(|| m.chr - REG_OFFSET)
}
fn name_into(e: &Engine, tokens: &[Token], name: &mut Vec<u8>) -> Option<()> {
let the = cs(e, N::The)?;
let the = e.eqtb.meaning(the);
if the.cmd != Cmd::The || the.mac.is_some() {
return None;
}
let the_chr = the.chr;
let mut stack: Vec<(&[Token], usize)> = vec![(tokens, 0)];
let mut budget = 200u32;
while let Some((list, k)) = stack.last_mut() {
let Some(&t) = list.get(*k) else {
stack.pop();
continue;
};
*k += 1;
if !t.is_cs() {
crate::utf8x::push(name, t.chr());
continue;
}
budget = budget.checked_sub(1)?;
if stack.len() > 50 {
return None;
}
let m = e.eqtb.meaning(t.cs_id());
match m.cmd {
Cmd::Call | Cmd::LongCall => {
let mac = m.mac.as_ref()?;
if mac.tokens.first() != Some(&Token::END_MATCH) {
return None;
}
stack.push((&mac.tokens[1..], 0));
}
Cmd::The if m.chr == the_chr && m.mac.is_none() => {
let (list, k) = stack.last_mut()?;
let r = *list.get(*k)?;
*k += 1;
if !r.is_cs() {
return None;
}
let reg = toks_register(e, r.cs_id())?;
stack.push((&e.eqtb.toks[reg as usize].val, 0));
}
_ => return None,
}
}
Some(())
}
fn key_cs(e: &Engine, parts: &[&[Token]], suffix: &[u8]) -> Result<Option<CsId>, ()> {
let mut name = b"pgfk@".to_vec();
for p in parts {
name_into(e, p, &mut name).ok_or(())?;
}
name.extend_from_slice(suffix);
Ok(e.hash
.lookup(&name)
.filter(|&cs| e.eqtb.meaning(cs).cmd != Cmd::UndefinedCs))
}
fn expand_once(e: &Engine, cs: CsId) -> Option<Vec<Token>> {
let m = e.eqtb.meaning(cs);
match m.cmd {
Cmd::Call | Cmd::LongCall => {
let mac = m.mac.as_ref()?;
if mac.tokens.first() != Some(&Token::END_MATCH) {
return None;
}
Some(mac.tokens[1..].to_vec())
}
c if !c.is_expandable() => Some(vec![Token::cs(cs)]),
_ => None,
}
}
fn plain_tokens(e: &Engine, t: &[Token], par: Option<CsId>) -> bool {
t.iter().all(|&t| {
if !t.is_cs() {
return !is_param(t);
}
if Some(t.cs_id()) == par {
return false;
}
!matches!(
e.eqtb.meaning(t.cs_id()).cmd,
Cmd::OuterCall | Cmd::LongOuterCall
)
})
}
#[derive(Clone, Copy)]
enum N {
Stop,
Relax,
PossibleSpace,
SpToken,
TempToks,
Key,
PossibleSlash,
KeyRaw,
AddedFlag,
DefaultPath,
IfTrue,
IfFalse,
Par,
PathToks,
Temp,
Name,
Value,
Code,
Eov,
The,
CaseTwo,
CaseTwoExtern,
CaseThree,
Unknown,
NoValueText,
Path,
HandlersUnknown,
Empty,
ValueRequired,
CaseOne,
Fi,
}
const N_NAMES: &[&[u8]] = &[
b"pgfkeys@stop",
b"relax",
b"pgfkeys@possiblespace",
b"pgfkeys@sptoken",
b"pgfkeys@temptoks",
b"pgfkeyscurrentkey",
b"pgfkeys@possibleslash",
b"pgfkeyscurrentkeyRAW",
b"ifpgfkeysaddeddefaultpath",
b"pgfkeysdefaultpath",
b"iftrue",
b"iffalse",
b"par",
b"pgfkeys@pathtoks",
b"pgfkeys@temp",
b"pgfkeyscurrentname",
b"pgfkeyscurrentvalue",
b"pgfkeys@code",
b"pgfeov",
b"the",
b"pgfkeys@case@two",
b"pgfkeys@case@two@extern",
b"pgfkeys@case@three",
b"pgfkeys@unknown",
b"pgfkeysnovalue@text",
b"pgfkeyscurrentpath",
b"pgfk@/handlers/.unknown/.@cmd",
b"pgfkeys@empty",
b"pgfkeysvaluerequired",
b"pgfkeys@case@one",
b"fi",
];
thread_local! {
static IDS: [std::cell::Cell<CsId>; N_NAMES.len()] =
std::array::from_fn(|_| std::cell::Cell::new(UNDEFINED_CS));
}
fn cs(e: &Engine, n: N) -> Option<CsId> {
let name = N_NAMES[n as usize];
IDS.with(|ids| {
let c = ids[n as usize].get();
if (c.0 as usize) < e.hash.len() && e.hash.name(c) == name {
return Some(c);
}
let c = e.hash.lookup(name)?;
ids[n as usize].set(c);
Some(c)
})
}
fn lookup(e: &Engine, names: &[N]) -> Option<Vec<CsId>> {
names.iter().map(|&n| cs(e, n)).collect()
}
struct SpIds {
stop: CsId,
relax: CsId,
possible: CsId,
sptoken: CsId,
temptoks: CsId,
reg: u32,
}
fn sp_ids(e: &Engine) -> Option<SpIds> {
let ids = lookup(
e,
&[N::Stop, N::Relax, N::PossibleSpace, N::SpToken, N::TempToks],
)?;
Some(SpIds {
stop: ids[0],
relax: ids[1],
possible: ids[2],
sptoken: ids[3],
temptoks: ids[4],
reg: toks_register(e, ids[4])?,
})
}
fn trim(
e: &Engine,
ids: &SpIds,
target: Token,
value: &[Token],
) -> Result<(Meaning, Vec<Token>), &'static str> {
if !target.is_cs() {
return Err("target not a control sequence");
}
if target.cs_id() == ids.possible || target.cs_id() == ids.temptoks {
return Err("target assigned by the code");
}
let (stop, relax, sp) = (Token::cs(ids.stop), Token::cs(ids.relax), Token::SPACE);
let first = value.first().copied().unwrap_or(stop);
let first_meaning = token_meaning(e, first);
if matches!(first_meaning.cmd, Cmd::OuterCall | Cmd::LongOuterCall) {
return Err("outer");
}
let mut s = Vec::with_capacity(value.len() + 7);
if !ifx(&first_meaning, e.eqtb.meaning(ids.sptoken)) {
s.push(sp);
}
s.extend_from_slice(value);
s.extend_from_slice(&[stop, stop, sp, stop, relax, target]);
if s[0] != sp {
return Err("implicit space");
}
let (end, next) = delimited(&s, 1, &[sp, stop]).ok_or("unmatched")?;
let mut c = strip(&s[1..end]).to_vec();
c.extend_from_slice(&s[next..]);
let (end1, next1) = delimited(&c, 0, &[stop]).ok_or("unmatched")?;
let (_, next2) = delimited(&c, next1, &[relax]).ok_or("unmatched")?;
if c[next2..] != [target] {
return Err("unusual value");
}
let result = strip(&c[..end1]).to_vec();
Ok((first_meaning, result))
}
fn commit_trim(e: &mut Engine, ids: &SpIds, target: CsId, first: Meaning, result: &[Token]) {
e.eq_define(Loc::Cs(ids.possible), Val::Meaning(first));
e.eq_define(Loc::Toks(ids.reg), Val::Toks(token_list(result)));
e.eq_define(Loc::Cs(target), Val::Meaning(macro_meaning(result)));
}
fn run_spdef(e: &mut Engine, args: &[TokenList]) -> bool {
const W: &str = "pgfkeys@spdef";
if !plain_state(e) {
return false;
}
let [target] = &args[0][..] else {
return why(W, "target not one token");
};
if !SP_DEPS.with(|d| d.borrow_mut().check(e)) {
return why(W, "dependencies");
}
let Some(ids) = sp_ids(e) else {
return false;
};
let (first, result) = match trim(e, &ids, *target, &args[1]) {
Ok(r) => r,
Err(reason) => return why(W, reason),
};
commit_trim(e, &ids, target.cs_id(), first, &result);
true
}
struct PathIds {
key: CsId,
possible: CsId,
raw: CsId,
flag: CsId,
default: CsId,
relax: CsId,
iftrue: CsId,
iffalse: CsId,
par: CsId,
}
fn path_ids(e: &Engine) -> Option<PathIds> {
let ids = lookup(
e,
&[
N::Key,
N::PossibleSlash,
N::KeyRaw,
N::AddedFlag,
N::DefaultPath,
N::Relax,
N::IfTrue,
N::IfFalse,
N::Par,
],
)?;
Some(PathIds {
key: ids[0],
possible: ids[1],
raw: ids[2],
flag: ids[3],
default: ids[4],
relax: ids[5],
iftrue: ids[6],
iffalse: ids[7],
par: ids[8],
})
}
type PathResult = (Meaning, Option<(Vec<Token>, Vec<Token>)>);
fn add_path(e: &Engine, ids: &PathIds, body: &[Token]) -> Result<PathResult, &'static str> {
if !plain_tokens(e, body, Some(ids.par)) {
return Err("unusual key");
}
let relax_t = Token::cs(ids.relax);
let first = body.first().copied().unwrap_or(relax_t);
let first_meaning = token_meaning(e, first);
let slash = Meaning::prim(Cmd::OtherChar, u32::from(b'/'));
let mut s = body.to_vec();
s.push(relax_t);
let (end, next) = delimited(&s, 0, &[relax_t]).ok_or("unmatched")?;
if next != s.len() {
return Err("\\relax in the key");
}
if ifx(&first_meaning, &slash) {
return Ok((first_meaning, None));
}
let arg = strip(&s[..end]).to_vec();
let mut t = vec![Token::cs(ids.default)];
t.extend_from_slice(&arg);
let x = deps::edef(e, &t).ok_or("default path")?;
if x.iter().any(|&t| is_param(t)) {
return Err("parameter characters");
}
Ok((first_meaning, Some((arg, x))))
}
fn commit_path(e: &mut Engine, ids: &PathIds, r: PathResult) {
e.eq_define(Loc::Cs(ids.possible), Val::Meaning(r.0));
match r.1 {
None => {
let f = e.eqtb.meaning(ids.iffalse).clone();
e.eq_define(Loc::Cs(ids.flag), Val::Meaning(f));
let k = e.eqtb.meaning(ids.key).clone();
e.eq_define(Loc::Cs(ids.raw), Val::Meaning(k));
}
Some((raw, key)) => {
let t = e.eqtb.meaning(ids.iftrue).clone();
e.eq_define(Loc::Cs(ids.flag), Val::Meaning(t));
e.eq_define(Loc::Cs(ids.raw), Val::Meaning(macro_meaning(&raw)));
e.eq_define(Loc::Cs(ids.key), Val::Meaning(macro_meaning(&key)));
}
}
}
fn run_add_path(e: &mut Engine, _: &[TokenList]) -> bool {
const W: &str = "pgfkeys@add@path@as@needed";
if !plain_state(e) {
return false;
}
if !PATH_DEPS.with(|d| d.borrow_mut().check(e)) {
return why(W, "dependencies");
}
let Some(ids) = path_ids(e) else {
return false;
};
let Some(body) = deps::plain_body(e, ids.key) else {
return why(W, "key not a macro");
};
let r = match add_path(e, &ids, body) {
Ok(r) => r,
Err(reason) => return why(W, reason),
};
commit_path(e, &ids, r);
true
}
fn open_ifx_else(e: &mut Engine) {
use crate::cond::{CondRecord, FI_CODE, if_codes::IFX};
let c = &mut e.cond;
c.stack.push(CondRecord {
if_limit: c.if_limit,
cur_if: c.cur_if,
if_line: c.if_line,
});
c.cur_if = IFX;
c.if_limit = FI_CODE;
c.if_line = e.input.line();
}
fn run_unpack(e: &mut Engine, args: &[TokenList]) -> bool {
const W: &str = "pgfkeys@unpack";
if !plain_state(e) {
return false;
}
for (deps, label) in [
(&UNPACK_DEPS, "dependencies"),
(&SP_DEPS, "spdef dependencies"),
(&PATH_DEPS, "path dependencies"),
(&KEY_DEPS, "key dependencies"),
] {
if !deps.with(|d| d.borrow_mut().check(e)) {
return why(W, label);
}
}
let (Some(sp), Some(p), Some(ids)) = (
sp_ids(e),
path_ids(e),
lookup(
e,
&[
N::Value,
N::Empty,
N::NoValueText,
N::ValueRequired,
N::CaseOne,
N::Fi,
],
),
) else {
return false;
};
let [value, empty, novalue, required, case_one, fi] = ids[..] else {
return false;
};
let (kfirst, kt) = match trim(e, &sp, Token::cs(p.key), &args[0]) {
Ok(r) => r,
Err(reason) => return why(W, reason),
};
if kt
.iter()
.any(|&t| is_param(t) || t == Token::cs(p.key) || t == Token::cs(sp.possible))
{
return why(W, "unusual key");
}
let Some(k) = deps::edef(e, &kt) else {
return why(W, "key expansion");
};
if k.iter().any(|&t| is_param(t)) {
return why(W, "parameter characters");
}
if ifx(¯o_meaning(&k), e.eqtb.meaning(empty)) {
commit_trim(e, &sp, p.key, kfirst, &kt);
e.eq_define(Loc::Cs(p.key), Val::Meaning(macro_meaning(&k)));
return true;
}
let path = match add_path(e, &p, &k) {
Ok(r) => r,
Err(reason) => return why(W, reason),
};
let (vfirst, vt) = match trim(e, &sp, Token::cs(value), &args[1]) {
Ok(r) => r,
Err(reason) => return why(W, reason),
};
let mut vmeaning = macro_meaning(&vt);
let mut default = None;
if ifx(&vmeaning, e.eqtb.meaning(novalue)) {
let key = path.1.as_ref().map_or(&k, |(_, x)| x);
let d = match key_cs(e, &[key], b"/.@def") {
Ok(d) => d,
Err(()) => return why(W, "key name"),
};
if let Some(d) = d {
vmeaning = e.eqtb.meaning(d).clone();
}
default = Some(d);
}
if ifx(&vmeaning, e.eqtb.meaning(required)) {
return why(W, "value required");
}
commit_trim(e, &sp, p.key, kfirst, &kt);
e.eq_define(Loc::Cs(p.key), Val::Meaning(macro_meaning(&k)));
commit_path(e, &p, path);
commit_trim(e, &sp, value, vfirst, &vt);
match default {
Some(Some(d)) => {
e.eq_define(Loc::Cs(value), Val::Meaning(vmeaning));
e.last_cs_name = d;
}
Some(None) => e.last_cs_name = UNDEFINED_CS,
None => {}
}
open_ifx_else(e);
open_ifx_else(e);
e.native_insert(vec![Token::cs(case_one), Token::cs(fi), Token::cs(fi)]);
true
}
fn split(e: &Engine, key: CsId) -> Result<(Vec<Token>, Vec<Token>), &'static str> {
let body = deps::plain_body(e, key).ok_or("key not a macro")?;
let par = cs(e, N::Par);
if !plain_tokens(e, body, par) {
return Err("unusual key");
}
let slash = Token::other(u32::from(b'/'));
let mut s = body.to_vec();
s.extend_from_slice(&[slash, slash]);
let mut path: Vec<Token> = Vec::new();
let mut i = 0;
let name = loop {
let (e1, n1) = delimited(&s, i, &[slash]).ok_or("unmatched")?;
let (e2, n2) = delimited(&s, n1, &[slash]).ok_or("unmatched")?;
let a1 = strip(&s[i..e1]).to_vec();
let a2 = strip(&s[n1..e2]).to_vec();
if a2.is_empty() {
if n2 != s.len() {
return Err("empty path element");
}
break a1;
}
path.extend_from_slice(&a1);
path.push(slash);
let mut rest = a2;
rest.push(slash);
rest.extend_from_slice(&s[n2..]);
s = rest;
i = 0;
};
if !path.is_empty() {
let relax = Token::cs(cs(e, N::Relax).ok_or("relax")?);
path.push(relax);
let (end, next) = delimited(&path, 0, &[slash, relax]).ok_or("unmatched")?;
if next != path.len() {
return Err("unusual path");
}
path = strip(&path[..end]).to_vec();
}
Ok((path, name))
}
fn commit_split(e: &mut Engine, ids: &SplitIds, path: &[Token], name: &[Token]) {
e.eq_define(Loc::Toks(ids.pathtoks), Val::Toks(token_list(path)));
e.eq_define(Loc::Cs(ids.temp), Val::Meaning(macro_meaning(&[])));
e.eq_define(Loc::Cs(ids.name), Val::Meaning(macro_meaning(name)));
}
struct SplitIds {
key: CsId,
pathtoks: u32,
temp: CsId,
name: CsId,
}
fn split_ids(e: &Engine) -> Option<SplitIds> {
let ids = lookup(e, &[N::Key, N::PathToks, N::Temp, N::Name])?;
Some(SplitIds {
key: ids[0],
pathtoks: toks_register(e, ids[1])?,
temp: ids[2],
name: ids[3],
})
}
fn run_split_path(e: &mut Engine, _: &[TokenList]) -> bool {
const W: &str = "pgfkeys@split@path";
if !plain_state(e) {
return false;
}
if !SPLIT_DEPS.with(|d| d.borrow_mut().check(e)) {
return why(W, "dependencies");
}
let Some(ids) = split_ids(e) else {
return false;
};
let (path, name) = match split(e, ids.key) {
Ok(r) => r,
Err(reason) => return why(W, reason),
};
commit_split(e, &ids, &path, &name);
true
}
struct KeyIds {
key: CsId,
value: CsId,
code: CsId,
eov: CsId,
relax: CsId,
}
fn key_ids(e: &Engine) -> Option<KeyIds> {
let ids = lookup(e, &[N::Key, N::Value, N::Code, N::Eov, N::Relax])?;
Some(KeyIds {
key: ids[0],
value: ids[1],
code: ids[2],
eov: ids[3],
relax: ids[4],
})
}
fn key_checks(e: &Engine, what: &str) -> Option<KeyIds> {
if !plain_state(e) {
return None;
}
if !KEY_DEPS.with(|d| d.borrow_mut().check(e)) {
why(what, "dependencies");
return None;
}
key_ids(e)
}
fn call_tokens(e: &Engine, ids: &KeyIds) -> Option<Vec<Token>> {
let mut t = vec![Token::cs(ids.code)];
t.extend(expand_once(e, ids.value)?);
t.push(Token::cs(ids.eov));
Some(t)
}
fn let_code(e: &mut Engine, ids: &KeyIds, cmd: CsId) {
let m = e.eqtb.meaning(cmd).clone();
e.eq_define(Loc::Cs(ids.code), Val::Meaning(m));
e.last_cs_name = cmd;
}
fn insert_cs(e: &mut Engine, name: N) -> bool {
let Some(cs) = cs(e, name) else {
return false;
};
e.native_insert(vec![Token::cs(cs)]);
true
}
fn run_case_one(e: &mut Engine, _: &[TokenList]) -> bool {
const W: &str = "pgfkeys@case@one";
let Some(ids) = key_checks(e, W) else {
return false;
};
let key = [Token::cs(ids.key)];
let Ok(cmd) = key_cs(e, &[&key], b"/.@cmd") else {
return why(W, "key name");
};
let Some(cmd) = cmd else {
if !insert_cs(e, N::CaseTwo) {
return false;
}
e.last_cs_name = UNDEFINED_CS;
return true;
};
let relax = e.eqtb.meaning(ids.relax);
if ifx(e.eqtb.meaning(cmd), relax) {
if cs(e, N::Unknown).is_none() {
return false;
}
let_code(e, &ids, cmd);
return insert_cs(e, N::Unknown);
}
let Some(tokens) = call_tokens(e, &ids) else {
return why(W, "value");
};
let_code(e, &ids, cmd);
e.native_insert(tokens);
true
}
fn run_case_two(e: &mut Engine, _: &[TokenList]) -> bool {
const W: &str = "pgfkeys@case@two";
let Some(ids) = key_checks(e, W) else {
return false;
};
let key = [Token::cs(ids.key)];
let Ok(cs) = key_cs(e, &[&key], b"") else {
return why(W, "key name");
};
let next = if cs.is_some() {
N::CaseTwoExtern
} else {
N::CaseThree
};
if !insert_cs(e, next) {
return false;
}
e.last_cs_name = cs.unwrap_or(UNDEFINED_CS);
true
}
fn run_case_two_extern(e: &mut Engine, _: &[TokenList]) -> bool {
const W: &str = "pgfkeys@case@two@extern";
let Some(ids) = key_checks(e, W) else {
return false;
};
let Some(novalue) = cs(e, N::NoValueText) else {
return false;
};
let value = e.eqtb.meaning(ids.value).clone();
if ifx(&value, e.eqtb.meaning(novalue)) {
return why(W, "no value");
}
let mut name = b"pgfk@".to_vec();
if name_into(e, &[Token::cs(ids.key)], &mut name).is_none() {
return why(W, "key name");
}
let cs = e.hash.lookup_or_insert(&name);
e.eq_define(Loc::Cs(cs), Val::Meaning(value));
e.last_cs_name = cs;
true
}
fn run_case_three(e: &mut Engine, _: &[TokenList]) -> bool {
const W: &str = "pgfkeys@case@three";
let Some(ids) = key_checks(e, W) else {
return false;
};
if !SPLIT_DEPS.with(|d| d.borrow_mut().check(e)) {
return why(W, "dependencies");
}
let Some(sids) = split_ids(e) else {
return false;
};
let (path, name) = match split(e, sids.key) {
Ok(r) => r,
Err(reason) => return why(W, reason),
};
let mut hname = b"pgfk@/handlers/".to_vec();
if name_into(e, &name, &mut hname).is_none() {
return why(W, "handler name");
}
hname.extend_from_slice(b"/.@cmd");
let handler = e
.hash
.lookup(&hname)
.filter(|&cs| e.eqtb.meaning(cs).cmd != Cmd::UndefinedCs);
let tokens = match handler {
Some(_) => {
let Some(t) = call_tokens(e, &ids) else {
return why(W, "value");
};
t
}
None => {
let Some(u) = cs(e, N::Unknown) else {
return false;
};
vec![Token::cs(u)]
}
};
commit_split(e, &sids, &path, &name);
match handler {
Some(h) => let_code(e, &ids, h),
None => e.last_cs_name = UNDEFINED_CS,
}
e.native_insert(tokens);
true
}
fn run_unknown(e: &mut Engine, _: &[TokenList]) -> bool {
const W: &str = "pgfkeys@unknown";
let Some(ids) = key_checks(e, W) else {
return false;
};
let Some(path) = cs(e, N::Path) else {
return false;
};
let Ok(cmd) = key_cs(e, &[&[Token::cs(path)]], b"/.unknown/.@cmd") else {
return why(W, "path");
};
let cmd = match cmd {
Some(c) => c,
None => match cs(e, N::HandlersUnknown)
.filter(|&cs| e.eqtb.meaning(cs).cmd != Cmd::UndefinedCs)
{
Some(c) => c,
None => return why(W, "no /handlers/.unknown"),
},
};
let Some(tokens) = call_tokens(e, &ids) else {
return why(W, "value");
};
let_code(e, &ids, cmd);
e.native_insert(tokens);
true
}