use std::cell::RefCell;
use super::Native;
use super::deps::Deps;
use crate::Engine;
use crate::cmd::Cmd;
use crate::token::{Token, TokenList};
pub(super) const PARSE: Native = Native {
name: "keyval_parse:nnn",
fingerprints: &[(22, 0x6edb_e358_624f_a9ee)],
executes: false,
run,
};
const NAMES: &[&str] = &[
"__kernel_exp_not:w",
"tex_expanded:D",
"__keyval_loop_active:nnw",
"__keyval_loop_other:nnw",
"__keyval_split_other:w",
"__keyval_split_active:w",
"__keyval_split_active_auxi:w",
"__keyval_split_active_auxii:w",
"__keyval_split_active_auxiii:w",
"__keyval_split_active_auxiv:w",
"__keyval_split_active_auxv:w",
"__keyval_split_other_auxi:w",
"__keyval_split_other_auxii:w",
"__keyval_split_other_auxiii:w",
"__keyval_clean_up_active:w",
"__keyval_clean_up_other:w",
"__keyval_end_loop_other:w",
"__keyval_end_loop_active:w",
"__keyval_pair:nnnn",
"__keyval_key:nn",
"__keyval_if_empty:w",
"__keyval_if_blank:w",
"__keyval_if_recursion_tail:w",
"__keyval_blank_true:w",
"__keyval_trim:nN",
"__keyval_trim_auxi:w",
"__keyval_trim_auxii:w",
"__keyval_trim_auxiii:w",
"__keyval_trim_auxiv:w",
"exp_not:n",
];
const KNOWN: &[u64] = &[0x5e6b_f026_f83c_4c2c];
thread_local! {
static DEPS: RefCell<Deps> = RefCell::new(Deps::new("keyval", NAMES, KNOWN));
}
fn why(reason: &str) -> bool {
if super::debug() {
eprintln!("[native] keyval_parse declines ({reason})");
}
false
}
fn is_brace(t: Token, cmd: Cmd) -> bool {
!t.is_cs() && t.cmd_code() == cmd as u32
}
fn is_space(t: Token) -> bool {
!t.is_cs() && t.cmd_code() == Cmd::Spacer as u32
}
fn is_delim(e: &Engine, t: Token, c: u8) -> bool {
if t.is_cs() {
matches!(e.hash.kind(t.cs_id()), crate::hash::CsKind::Active(a) if *a == u32::from(c))
} else {
t.cmd_code() == Cmd::OtherChar as u32 && t.chr() == u32::from(c)
}
}
fn split(toks: &[Token], mut delim: impl FnMut(Token) -> bool) -> Vec<&[Token]> {
let mut out = Vec::new();
let mut depth = 0i32;
let mut start = 0;
for (i, &t) in toks.iter().enumerate() {
if is_brace(t, Cmd::LeftBrace) {
depth += 1;
} else if is_brace(t, Cmd::RightBrace) {
depth -= 1;
} else if depth == 0 && delim(t) {
out.push(&toks[start..i]);
start = i + 1;
}
}
out.push(&toks[start..]);
out
}
fn single_group(toks: &[Token]) -> bool {
if toks.len() < 2 || !is_brace(toks[0], Cmd::LeftBrace) {
return false;
}
let mut depth = 0;
for (i, &t) in toks.iter().enumerate() {
if is_brace(t, Cmd::LeftBrace) {
depth += 1;
} else if is_brace(t, Cmd::RightBrace) {
depth -= 1;
if depth == 0 {
return i == toks.len() - 1;
}
}
}
false
}
fn trim(toks: &[Token]) -> &[Token] {
let mut a = 0;
let mut b = toks.len();
while a < b && is_space(toks[a]) {
a += 1;
}
while b > a && is_space(toks[b - 1]) {
b -= 1;
}
let t = &toks[a..b];
if single_group(t) {
&t[1..t.len() - 1]
} else {
t
}
}
fn blank(toks: &[Token]) -> bool {
let mut i = 0;
while i < toks.len() && is_space(toks[i]) {
i += 1;
}
i == toks.len()
|| (is_brace(toks[i], Cmd::LeftBrace)
&& toks
.get(i + 1)
.is_some_and(|&t| is_brace(t, Cmd::RightBrace)))
}
fn run(e: &mut Engine, args: &[TokenList]) -> bool {
if !DEPS.with(|d| d.borrow_mut().check(e)) {
return why("dependencies");
}
let (f1, f2, list) = (&args[0], &args[1], &args[2]);
let marks: Vec<Token> = [
&b"s__keyval_nil"[..],
b"s__keyval_mark",
b"s__keyval_stop",
b"s__keyval_tail",
]
.iter()
.filter_map(|n| e.hash.lookup(n).map(Token::cs))
.collect();
let bad = |t: &Token| {
marks.contains(t)
|| (!t.is_cs() && t.cmd_code() == Cmd::CarRet as u32)
|| (is_space(*t) && t.chr() != u32::from(b' '))
};
if list.iter().any(bad) || f1.iter().any(bad) || f2.iter().any(bad) {
return why("tokens");
}
let Some(exp_not) = e.hash.lookup(b"__kernel_exp_not:w").map(Token::cs) else {
return false;
};
let mut out = vec![exp_not, Token::LEFT_BRACE];
for item in split(list, |t| is_delim(e, t, b',')) {
if item.iter().all(|&t| is_space(t)) {
continue;
}
let parts = split(item, |t| is_delim(e, t, b'='));
match parts[..] {
[key] => {
let k = trim(key);
if blank(k) {
return why("blank key");
}
out.extend_from_slice(f1);
out.push(Token::LEFT_BRACE);
out.extend_from_slice(k);
out.push(Token::RIGHT_BRACE);
}
[key, value] => {
let k = trim(key);
if blank(k) {
return why("blank key");
}
out.extend_from_slice(f2);
out.push(Token::LEFT_BRACE);
out.extend_from_slice(k);
out.push(Token::RIGHT_BRACE);
out.push(Token::LEFT_BRACE);
out.extend_from_slice(trim(value));
out.push(Token::RIGHT_BRACE);
}
_ => return why("misplaced ="),
}
}
out.push(Token::RIGHT_BRACE);
e.native_insert(out);
true
}