use std::rc::Rc;
use mlua::{Lua, MultiValue, Table, Value, Variadic};
use retex_engine::Engine;
use retex_engine::cmd::Cmd;
use retex_engine::eqtb::{CodeKind, Loc, MU_GLUE_OFFSET, Val};
use retex_engine::glue::Glue;
use retex_engine::input::{CatMode, LuaItem, LuaLineKind};
use retex_engine::primitives_gen::Group;
use retex_engine::scan::REG_OFFSET;
use crate::{create_function_caller_errors, create_function_plain_errors, push_print, with_engine};
pub fn install(lua: &Lua) -> mlua::Result<()> {
let tex = lua.create_table()?;
crate::nestlib::install(lua, &tex)?;
tex.set(
"enableprimitives",
lua.create_function(|lua, (prefix, names): (mlua::String, Table)| {
let prefix = prefix.as_bytes().to_vec();
let mut list: Vec<Vec<u8>> = Vec::new();
for v in names.sequence_values::<mlua::String>() {
list.push(v?.as_bytes().to_vec());
}
with_engine(lua, |e| {
for name in list {
let p: &[u8] = if !prefix.is_empty() && name.starts_with(&prefix) {
b""
} else {
&prefix
};
e.define_primitive(p, &name);
}
})
})?,
)?;
tex.set(
"extraprimitives",
lua.create_function(|lua, groups: Variadic<String>| {
let t = lua.create_table()?;
let all = groups.is_empty();
let wanted = |g: Group| {
(all && matches!(g, Group::Etex | Group::Luatex))
|| groups.iter().any(|s| match s.as_str() {
"tex" => g == Group::Tex,
"core" => g == Group::Core,
"etex" => g == Group::Etex,
"luatex" => g == Group::Luatex,
_ => false,
})
};
let mut i = 1;
for p in retex_engine::primitives_gen::PRIMITIVES {
if p.group != Group::None && wanted(p.group) {
t.set(i, p.name)?;
i += 1;
}
}
Ok(t)
})?,
)?;
tex.set("getmath", create_function_caller_errors(lua, get_math)?)?;
tex.set("linebreak", create_function_plain_errors(lua, line_break)?)?;
install_more(lua, &tex)?;
tex.set(
"shipout",
lua.create_function(|lua, n: Value| {
let n = match n {
Value::Integer(n) if (0..=65535).contains(&n) => n as usize,
Value::Number(x) if (0.0..=65535.0).contains(&x) => x as usize,
_ => 0,
};
with_engine(lua, |e| {
let b = e.eqtb.boxes[n].val;
e.eqtb.boxes[n].val = retex_engine::nodes::NodeRef::NULL;
e.ship_out(b);
})
})?,
)?;
tex.set(
"primitives",
lua.create_function(|lua, ()| {
let t = lua.create_table()?;
for (i, p) in retex_engine::primitives_gen::PRIMITIVES.iter().enumerate() {
t.set(i + 1, p.name)?;
}
Ok(t)
})?,
)?;
tex.set(
"print",
lua.create_function(|lua, args: MultiValue| print_items(lua, args, LuaLineKind::Print))?,
)?;
tex.set(
"sprint",
lua.create_function(|lua, args: MultiValue| print_items(lua, args, LuaLineKind::Sprint))?,
)?;
tex.set(
"tprint",
lua.create_function(|lua, args: Variadic<Table>| {
for t in args {
let mut v: Vec<Value> = Vec::new();
for x in t.sequence_values::<Value>() {
v.push(x?);
}
print_items(lua, MultiValue::from_iter(v), LuaLineKind::Sprint)?;
}
Ok(())
})?,
)?;
tex.set(
"cprint",
lua.create_function(|lua, args: MultiValue| {
let mut it = args.into_iter();
let cat = match it.next() {
Some(Value::Integer(n)) => n,
Some(Value::Number(n)) if n.fract() == 0.0 => n as i64,
Some(Value::Number(_)) => 0,
_ => 12,
};
let cat = u8::try_from(cat).ok().filter(|&c| c <= 15).unwrap_or(12);
let args: Vec<Value> = it.collect();
let items: Vec<Value> = match args.first() {
Some(Value::Table(t)) => t
.clone()
.sequence_values::<Value>()
.collect::<Result<_, _>>()?,
_ => args,
};
for item in items {
if let Value::UserData(u) = &item
&& let Ok(t) = u.borrow::<crate::tokenlib::LToken>()
{
push_print(lua, LuaItem::Tokens(vec![retex_engine::token::Token(t.0)]));
continue;
}
for s in strings_of(lua, vec![item])? {
push_print(
lua,
LuaItem::Text {
text: s,
kind: LuaLineKind::Sprint,
catmode: CatMode::Single(cat),
},
);
}
}
Ok(())
})?,
)?;
tex.set(
"write",
lua.create_function(|lua, args: MultiValue| {
for s in strings_of(lua, args.into_iter().collect())? {
push_print(
lua,
LuaItem::Text {
text: s,
kind: LuaLineKind::Sprint,
catmode: CatMode::String,
},
);
}
Ok(())
})?,
)?;
for (name, kind) in [
("count", Reg::Count),
("dimen", Reg::Dimen),
("skip", Reg::Skip),
("muskip", Reg::MuSkip),
("toks", Reg::Toks),
("attribute", Reg::Attr),
("box", Reg::Box),
] {
tex.set(name, register_table(lua, kind)?)?;
tex.set(
format!("set{name}"),
lua.create_function(move |lua, args: MultiValue| set_register(lua, kind, args))?,
)?;
tex.set(
format!("get{name}"),
lua.create_function(move |lua, k: Value| get_register(lua, kind, k))?,
)?;
tex.set("isattribute", lua.create_function(|_, _k: Value| Ok(true))?)?;
}
for (name, kind) in [
("catcode", CodeT::Cat),
("lccode", CodeT::Code(CodeKind::Lc)),
("uccode", CodeT::Code(CodeKind::Uc)),
("sfcode", CodeT::Code(CodeKind::Sf)),
("mathcode", CodeT::Code(CodeKind::Math)),
("delcode", CodeT::Code(CodeKind::Del)),
] {
tex.set(name, code_table(lua, kind, name)?)?;
let set = format!("set{name}");
tex.set(
set.clone(),
create_function_caller_errors(lua, move |lua, args: MultiValue| {
set_code(lua, kind, &set, args)
})?,
)?;
let get = format!("get{name}");
tex.set(
get.clone(),
create_function_caller_errors(lua, move |lua, args: MultiValue| {
get_code(lua, kind, &get, false, args)
})?,
)?;
if matches!(kind, CodeT::Code(CodeKind::Math | CodeKind::Del)) {
let gets = format!("get{name}s");
tex.set(
gets.clone(),
create_function_caller_errors(lua, move |lua, args: MultiValue| {
get_code(lua, kind, &gets, true, args)
})?,
)?;
}
}
tex.set(
"get",
lua.create_function(|lua, (name, all): (Value, Option<bool>)| {
let Value::String(name) = name else {
return Ok(MultiValue::from_iter([name]));
};
get_param(lua, &name.to_str()?, all)
})?,
)?;
tex.set(
"set",
lua.create_function(|lua, args: MultiValue| {
let (global, rest) = split_global(args);
let mut it = rest.into_iter();
let name = match it.next() {
Some(Value::String(s)) => s.to_str()?.to_owned(),
_ => return Err(mlua::Error::runtime("tex.set: parameter name expected")),
};
let v = it.next().unwrap_or(Value::Nil);
set_param(lua, global, &name, v)
})?,
)?;
tex.set(
"runtoks",
lua.create_function(|lua, args: MultiValue| {
let v: Vec<Value> = args.into_iter().collect();
match v.first() {
Some(Value::Function(f)) => {
let before = crate::take_printed(lua);
if !before.is_empty() {
with_engine(lua, |e| {
e.push_local_marker();
e.push_lua_output(before);
e.run_local_loop();
})?;
}
with_engine(lua, |e| e.push_local_marker())?;
let r = f.call::<()>(());
let items = crate::take_printed(lua);
with_engine(lua, |e| {
e.push_lua_output(items);
e.run_local_loop();
})?;
r
}
Some(Value::Integer(n)) => {
let n = *n as usize;
with_engine(lua, |e| {
let l = e.eqtb.toks[n.min(65535)].val.clone();
e.run_local(Some(l));
})
}
Some(Value::String(s)) => {
let name = s.as_bytes().to_vec();
with_engine(lua, |e| {
let l = e.hash.lookup(&name).and_then(|cs| {
let m = e.eqtb.meaning(cs);
if m.cmd == Cmd::AssignToks && m.chr >= REG_OFFSET {
Some(e.eqtb.toks[(m.chr - REG_OFFSET) as usize].val.clone())
} else {
None
}
});
e.run_local(l);
})
}
_ => Ok(()),
}
})?,
)?;
let init_rand = create_function_plain_errors(lua, |lua, v: Value| {
let seed = rand_arg(&v)?;
with_engine(lua, |e| e.randoms.init(seed))
})?;
tex.set("init_rand", init_rand.clone())?;
tex.set("lua_math_randomseed", init_rand)?;
tex.set(
"uniform_rand",
create_function_plain_errors(lua, |lua, v: Value| {
let n = rand_arg(&v)?;
with_engine(lua, |e| e.randoms.unif_rand(n))
})?,
)?;
tex.set(
"uniformdeviate",
lua.create_function(|lua, v: Value| {
let n = lua.coerce_integer(v)?.unwrap_or(0) as i32;
with_engine(lua, |e| e.randoms.unif_rand(n).to_string())
})?,
)?;
tex.set(
"normal_rand",
lua.create_function(|lua, _: MultiValue| with_engine(lua, |e| e.randoms.norm_rand()))?,
)?;
tex.set(
"lua_math_random",
create_function_plain_errors(lua, |lua, args: MultiValue| lua_math_random(lua, &args))?,
)?;
tex.set("round", lua.create_function(|_, x: f64| Ok(round_tex(x)))?)?;
tex.set(
"scale",
lua.create_function(|_, (x, d): (f64, f64)| Ok(round_tex(x * d)))?,
)?;
tex.set(
"sp",
lua.create_function(|lua, v: Value| match v {
Value::Integer(n) => Ok(n),
Value::Number(n) => Ok(n.round() as i64),
Value::String(s) => {
let s = s.to_str()?.to_owned();
with_engine(lua, |e| e.dimen_from_string(&s))
}
_ => Ok(0),
})?,
)?;
tex.set(
"error",
lua.create_function(|lua, (msg, help): (String, Option<Table>)| {
let mut lines: Vec<String> = Vec::new();
if let Some(t) = help {
for l in t.sequence_values::<String>() {
lines.push(l?);
}
}
with_engine(lua, |e| {
e.print_err(&msg);
e.help_owned(lines);
e.error();
})
})?,
)?;
tex.set(
"hashtokens",
lua.create_function(|lua, ()| {
with_engine(lua, |e| {
let t = lua.create_table_with_capacity(e.hash.len(), 0)?;
for (i, (n, _)) in e.hash.iter_named().enumerate() {
t.raw_set(i + 1, lua.create_string(n)?)?;
}
Ok::<_, mlua::Error>(t)
})?
})?,
)?;
let mt = lua.create_table()?;
mt.set(
"__index",
lua.create_function(|lua, (_t, k): (Table, String)| get_param(lua, &k, None))?,
)?;
mt.set(
"__newindex",
lua.create_function(|lua, (_t, k, v): (Table, String, Value)| {
set_param(lua, false, &k, v)
})?,
)?;
tex.set_metatable(Some(mt))?;
let lazy: mlua::Function = lua
.load(
r#"
local build = ...
local mt = {}
local function fill(t)
setmetatable(t, nil)
for i, n in ipairs(build()) do rawset(t, i, n) end
end
mt.__index = function(t, k) fill(t); return rawget(t, k) end
mt.__len = function(t) fill(t); return rawlen(t) end
mt.__pairs = function(t) fill(t); return next, t, nil end
return function() return setmetatable({}, mt) end
"#,
)
.set_name("=tex.hashtokens")
.call(tex.get::<mlua::Function>("hashtokens")?)?;
tex.set("hashtokens", lazy)?;
lua.globals().set("tex", tex)?;
let _ = Rc::new(());
Ok(())
}
fn round_tex(x: f64) -> i64 {
let r = x.round();
r.clamp(-2147483647.0, 2147483647.0) as i64
}
fn c_int(x: f64) -> i32 {
if x > -2_147_483_649.0 && x < 2_147_483_648.0 {
x as i32
} else {
i32::MIN
}
}
fn rand_arg(v: &Value) -> mlua::Result<i32> {
let x = match *v {
Value::Integer(i) => i as f64,
Value::Number(x) => x,
_ => return Err(mlua::Error::runtime("argument must be a number")),
};
Ok(c_int((x + 0.5).floor()))
}
fn lua_math_random(lua: &Lua, args: &MultiValue) -> mlua::Result<f64> {
const NAME: &str = "tex.lua_math_random";
let el_gordo = i32::MAX; let r = f64::from(with_engine(lua, |e| e.randoms.unif_rand(el_gordo))?) / f64::from(el_gordo);
let check = |i: usize| -> mlua::Result<f64> {
let v = args.get(i).cloned().unwrap_or(Value::Nil);
match lua.coerce_number(v.clone())? {
Some(x) => Ok(x),
None => {
let got = if i < args.len() {
v.type_name()
} else {
"no value"
};
Err(mlua::Error::runtime(format!(
"bad argument #{} to '{NAME}' (number expected, got {got})",
i + 1
)))
}
}
};
let empty = |i: usize| {
Err(mlua::Error::runtime(format!(
"bad argument #{i} to '{NAME}' (interval is empty)"
)))
};
let bounds = match args.len() {
0 => None,
1 => {
let u = check(0)?;
if u.is_nan() || u < 1.0 {
return empty(1);
}
Some((1.0, u))
}
2 => {
let (l, u) = (check(0)?, check(1)?);
if l.is_nan() || u.is_nan() || l > u {
return empty(2);
}
Some((l, u))
}
_ => return Err(mlua::Error::runtime("wrong number of arguments")),
};
Ok(match bounds {
None => r,
Some((l, u)) => f64::from(c_int((r * (u - l + 1.0)).floor())) + l,
})
}
fn strings_of(_lua: &Lua, args: Vec<Value>) -> mlua::Result<Vec<Vec<u8>>> {
let mut out = Vec::new();
let mut push = |v: &Value| -> bool {
match v {
Value::String(s) => {
out.push(s.as_bytes().to_vec());
true
}
Value::Integer(n) => {
out.push(n.to_string().into_bytes());
true
}
Value::Number(n) => {
out.push(lua_number_string(*n).into_bytes());
true
}
_ => false,
}
};
if let Some(Value::Table(t)) = args.first() {
for v in t.clone().sequence_values::<Value>() {
if !push(&v?) {
break;
}
}
} else {
for v in &args {
push(v);
}
}
Ok(out)
}
pub fn lua_number_string(n: f64) -> String {
if n.fract() == 0.0 && n.abs() < 1e15 {
format!("{n:.1}")
} else {
let s = format!("{n:.14e}");
let _ = s;
format!("{n}")
}
}
fn print_items(lua: &Lua, args: MultiValue, kind: LuaLineKind) -> mlua::Result<()> {
let mut v: Vec<Value> = args.into_iter().collect();
let mut catmode = CatMode::Current;
if let Some(first) = v.first() {
let n = match first {
Value::Integer(n) => Some(*n),
Value::Number(n) if v.len() > 1 => Some(*n as i64),
_ => None,
};
if let Some(n) = n
&& v.len() > 1
{
catmode = match n {
-1 => CatMode::Current,
-2 => CatMode::String,
n if n >= 0 => {
let exists =
with_engine(lua, |e| e.eqtb.catcode_tables.contains_key(&(n as u32)))?;
if exists {
CatMode::Table(n as u32)
} else {
CatMode::Current
}
}
_ => CatMode::Current,
};
v.remove(0);
}
}
let (items, table): (Vec<Value>, bool) = match v.first() {
Some(Value::Table(t)) => (
t.clone()
.sequence_values::<Value>()
.collect::<Result<_, _>>()?,
true,
),
_ => (v, false),
};
for item in items {
if let Value::UserData(u) = &item
&& let Ok(t) = u.borrow::<crate::tokenlib::LToken>()
{
push_print(lua, LuaItem::Tokens(vec![retex_engine::token::Token(t.0)]));
continue;
}
let Some(s) = strings_of(lua, vec![item])?.pop() else {
if table {
break;
}
continue;
};
push_print(
lua,
LuaItem::Text {
text: s,
kind,
catmode,
},
);
}
Ok(())
}
#[derive(Clone, Copy)]
enum Reg {
Count,
Dimen,
Skip,
MuSkip,
Toks,
Attr,
Box,
}
fn bad_reg_index(kind: Reg, set: bool) -> mlua::Error {
let what = if set { "set" } else { "get" };
mlua::Error::runtime(match kind {
Reg::Count => "incorrect count index".to_owned(),
Reg::Dimen => "incorrect dimen index".to_owned(),
Reg::Skip => "incorrect skip index".to_owned(),
Reg::MuSkip => "incorrect muskip index".to_owned(),
Reg::Attr => "incorrect attribute index".to_owned(),
Reg::Toks => format!("incorrect index specification for tex.{what}toks()"),
Reg::Box => format!("incorrect index specification for tex.{what}box()"),
})
}
fn reg_index(lua: &Lua, kind: Reg, k: &Value, set: bool) -> mlua::Result<u32> {
let number = |n: i64| {
u32::try_from(n)
.ok()
.filter(|&n| (n as usize) < retex_engine::eqtb::NUM_REGISTERS)
.ok_or_else(|| bad_reg_index(kind, set))
};
match k {
Value::Integer(n) => number(*n),
Value::Number(n) => number(*n as i64),
Value::String(s) => {
let name = s.as_bytes().to_vec();
let r = with_engine(lua, |e| {
let cs = e.hash.lookup(&name)?;
let m = e.eqtb.meaning(cs);
let ok = matches!(
(kind, m.cmd),
(Reg::Count, Cmd::AssignInt)
| (Reg::Dimen, Cmd::AssignDimen)
| (Reg::Skip, Cmd::AssignGlue)
| (Reg::MuSkip, Cmd::AssignMuGlue)
| (Reg::Toks, Cmd::AssignToks)
| (Reg::Attr, Cmd::AssignAttr)
| (Reg::Box, Cmd::CharGiven)
);
if !ok {
return None;
}
Some(if matches!(kind, Reg::Box) {
m.chr
} else {
m.chr.checked_sub(REG_OFFSET)?
})
})?;
r.ok_or_else(|| {
mlua::Error::runtime(format!("incorrect register name '{}'", s.to_string_lossy()))
})
}
_ => Err(mlua::Error::runtime("register index expected")),
}
}
fn glue_param_loc(k: &Value, kind: Reg) -> Option<Loc> {
let Value::String(s) = k else {
return None;
};
let p = retex_engine::primitives::lookup(&s.as_bytes())?;
match (kind, p.cmd) {
(Reg::Skip, Cmd::AssignGlue) => Some(Loc::GluePar(p.chr)),
(Reg::MuSkip, Cmd::AssignMuGlue) => Some(Loc::GluePar(p.chr + MU_GLUE_OFFSET)),
_ => None,
}
}
fn reg_loc(kind: Reg, n: u32) -> Loc {
match kind {
Reg::Count => Loc::Count(n),
Reg::Dimen => Loc::Dimen(n),
Reg::Skip => Loc::Skip(n),
Reg::MuSkip => Loc::MuSkip(n),
Reg::Toks => Loc::Toks(n),
Reg::Attr => Loc::Attr(n),
Reg::Box => Loc::Box(n),
}
}
fn get_register(lua: &Lua, kind: Reg, k: Value) -> mlua::Result<Value> {
let n = reg_index(lua, kind, &k, false)?;
let v = with_engine(lua, |e| e.eqtb.get(reg_loc(kind, n)))?;
val_to_lua(lua, v)
}
fn val_to_lua(lua: &Lua, v: Val) -> mlua::Result<Value> {
Ok(match v {
Val::Int(i) => Value::Integer(i),
Val::Glue(g) => Value::Table(glue_table(lua, &g)?),
Val::Toks(t) => {
let s = with_engine(lua, |e| {
e.print_to_string(|e| e.show_token_list(&t, 0, t.len(), i64::MAX))
})?;
Value::String(lua.create_string(&s)?)
}
Val::Node(n) => {
if n.is_null() {
Value::Nil
} else {
Value::UserData(lua.create_userdata(crate::nodelib::LNode(n.0))?)
}
}
_ => Value::Nil,
})
}
fn glue_table(lua: &Lua, g: &Glue) -> mlua::Result<Table> {
let t = lua.create_table()?;
t.set("width", g.width)?;
t.set("stretch", g.stretch)?;
t.set("shrink", g.shrink)?;
t.set("stretch_order", g.stretch_order)?;
t.set("shrink_order", g.shrink_order)?;
Ok(t)
}
fn lua_to_glue(lua: &Lua, v: &Value) -> mlua::Result<Glue> {
Ok(match v {
Value::Integer(n) => Glue::fixed(*n as i32),
Value::Number(n) => Glue::fixed(*n as i32),
Value::Table(t) => Glue {
width: t.get("width").unwrap_or(0),
stretch: t.get("stretch").unwrap_or(0),
shrink: t.get("shrink").unwrap_or(0),
stretch_order: t.get("stretch_order").unwrap_or(0),
shrink_order: t.get("shrink_order").unwrap_or(0),
},
Value::UserData(u) if u.is::<crate::nodelib::LNode>() => {
let p = retex_engine::nodes::NodeRef(u.borrow::<crate::nodelib::LNode>()?.0);
with_engine(lua, |e| e.nodes.glue_spec(p))?
}
other => return Err(not_a_node(lua, other)),
})
}
fn split_global(args: MultiValue) -> (bool, Vec<Value>) {
let mut v: Vec<Value> = args.into_iter().collect();
let global = matches!(v.first(), Some(Value::String(s)) if s.as_bytes() == b"global");
if global {
v.remove(0);
}
(global, v)
}
fn lua_to_val(lua: &Lua, kind: Reg, v: &Value) -> mlua::Result<Val> {
Ok(match kind {
Reg::Count | Reg::Dimen | Reg::Attr => Val::Int(match v {
Value::Integer(n) => *n,
Value::Number(n) => n.round() as i64,
Value::String(s) if matches!(kind, Reg::Dimen) => {
let s = s.to_str()?.to_owned();
with_engine(lua, |e| e.dimen_from_string(&s))?
}
_ => {
return Err(mlua::Error::runtime(match kind {
Reg::Count => "unsupported count value type",
Reg::Dimen => "unsupported dimen value type",
_ => "unsupported attribute value type",
}));
}
}),
Reg::Skip | Reg::MuSkip => match v {
Value::UserData(u) if u.is::<crate::nodelib::LNode>() => {
let p = retex_engine::nodes::NodeRef(u.borrow::<crate::nodelib::LNode>()?.0);
let t = with_engine(lua, |e| e.nodes.id(p))?;
if t != retex_engine::nodes::id::GLUE_SPEC {
return Err(mlua::Error::runtime("glue_spec expected"));
}
Val::Glue(lua_to_glue(lua, v)?)
}
other => return Err(not_a_node(lua, other)),
},
Reg::Toks => {
let Value::String(s) = v else {
return Err(mlua::Error::runtime("unsupported value type"));
};
let toks = retex_engine::the::str_toks(&s.as_bytes());
Val::Toks(Rc::from(toks))
}
Reg::Box => Val::Node(lua_to_box(lua, v)?),
})
}
fn lua_to_box(lua: &Lua, v: &Value) -> mlua::Result<retex_engine::nodes::NodeRef> {
use retex_engine::nodes::{NodeRef, id};
match v {
Value::Nil | Value::Boolean(_) => Ok(NodeRef::NULL),
Value::UserData(u) if u.is::<crate::nodelib::LNode>() => {
let p = NodeRef(u.borrow::<crate::nodelib::LNode>()?.0);
let t = with_engine(lua, |e| e.nodes.id(p))?;
if matches!(t, id::HLIST | id::VLIST) {
Ok(p)
} else {
Err(mlua::Error::runtime(format!(
"setbox: incompatible node type ({})",
crate::nodelib::type_name(t)
)))
}
}
other => Err(not_a_node(lua, other)),
}
}
pub(crate) fn not_a_node(lua: &Lua, v: &Value) -> mlua::Error {
let ty = match v {
Value::Integer(_) => "number",
_ => v.type_name(),
};
let msg = format!("lua <node> expected, not an object with type {ty}");
match with_engine(lua, |e| e.luatex_fatal("node lib", &msg)) {
Err(e) => e,
}
}
fn set_register(lua: &Lua, kind: Reg, args: MultiValue) -> mlua::Result<()> {
let (global, v) = split_global(args);
if v.len() < 2 {
return Err(mlua::Error::runtime("register and value expected"));
}
let n = reg_index(lua, kind, &v[0], true)?;
let val = lua_to_val(lua, kind, &v[1])?;
with_engine(lua, |e| store_register(e, global, kind, n, val))
}
fn store_register(e: &mut Engine, global: bool, kind: Reg, n: u32, val: Val) {
match (kind, val) {
(Reg::Box, Val::Node(b)) => e.set_box_register(n, global, b),
(_, val) => e.define(global, reg_loc(kind, n), val),
}
}
fn register_table(lua: &Lua, kind: Reg) -> mlua::Result<Table> {
let t = lua.create_table()?;
let mt = lua.create_table()?;
mt.set(
"__index",
lua.create_function(move |lua, (_t, k): (Table, Value)| get_register(lua, kind, k))?,
)?;
mt.set(
"__newindex",
lua.create_function(move |lua, (_t, k, v): (Table, Value, Value)| {
let n = reg_index(lua, kind, &k, true)?;
let val = lua_to_val(lua, kind, &v)?;
with_engine(lua, |e| store_register(e, false, kind, n, val))
})?,
)?;
t.set_metatable(Some(mt))?;
Ok(t)
}
#[derive(Clone, Copy)]
enum CodeT {
Cat,
Code(CodeKind),
}
fn code_loc(e: &Engine, kind: CodeT, table: Option<u32>, c: u32) -> Loc {
match kind {
CodeT::Cat => Loc::Cat {
table: table.unwrap_or_else(|| e.eqtb.cur_cat_table()),
ch: c,
},
CodeT::Code(k) => Loc::Code(k, c),
}
}
fn lua_int(v: &Value) -> i64 {
match v {
Value::Integer(n) => *n,
Value::Number(n) => *n as i64,
_ => 0,
}
}
fn code_int(v: &Value, name: &str) -> mlua::Result<i64> {
match v {
Value::Number(n) if n.fract() != 0.0 || n.abs() >= 9.2e18 => Err(mlua::Error::runtime(
format!("bad argument #3 to '{name}' (number has no integer representation)"),
)),
v => Ok(lua_int(v)),
}
}
fn check_char(n: i64, max: i64, name: &str) -> mlua::Result<u32> {
if (0..=max).contains(&n) {
Ok(n as u32)
} else {
Err(mlua::Error::runtime(format!(
"incorrect character value {n} for tex.{name}()"
)))
}
}
fn code_parts(kind: CodeT, v: i64) -> Vec<i64> {
use retex_engine::codes;
match kind {
CodeT::Code(CodeKind::Math) if v == codes::MATH_ACTIVE => vec![8, 0, 0],
CodeT::Code(CodeKind::Math) => vec![
i64::from(codes::math_class(v)),
i64::from(codes::math_fam(v)),
i64::from(codes::math_char(v)),
],
CodeT::Code(CodeKind::Del) => {
if v < 0 {
vec![-1, 0, 0, 0]
} else if v & (1 << 62) != 0 {
vec![
(v >> 53) & 0xFF,
(v >> 32) & 0x1F_FFFF,
(v >> 21) & 0xFF,
v & 0x1F_FFFF,
]
} else {
vec![(v >> 21) & 0xFF, v & 0x1F_FFFF, 0, 0]
}
}
_ => vec![v],
}
}
fn code_from_parts(kind: CodeT, t: &Table) -> mlua::Result<i64> {
let p = |i: i64| -> mlua::Result<i64> { Ok(lua_int(&t.get::<Value>(i)?)) };
Ok(match kind {
CodeT::Code(CodeKind::Math) => {
retex_engine::codes::math_code(p(1)? as u32, p(2)? as u32, p(3)? as u32)
}
CodeT::Code(CodeKind::Del) => {
if p(1)? < 0 {
-1
} else {
(p(1)? << 53) | (p(2)? << 32) | (p(3)? << 21) | p(4)? | (1 << 62)
}
}
_ => p(1)?,
})
}
fn code_value(lua: &Lua, kind: CodeT, v: i64) -> mlua::Result<Value> {
Ok(match kind {
CodeT::Code(CodeKind::Math | CodeKind::Del) => {
Value::Table(lua.create_sequence_from(code_parts(kind, v))?)
}
_ => Value::Integer(v),
})
}
fn read_code(lua: &Lua, kind: CodeT, table: Option<u32>, c: u32) -> mlua::Result<i64> {
with_engine(lua, |e| match e.eqtb.get(code_loc(e, kind, table, c)) {
Val::Int(i) => i,
_ => 0,
})
}
fn get_code(
lua: &Lua,
kind: CodeT,
name: &str,
parts: bool,
args: MultiValue,
) -> mlua::Result<MultiValue> {
let v: Vec<Value> = args.into_iter().collect();
let (table, c) = if v.len() >= 2 && matches!(kind, CodeT::Cat) {
(Some(lua_int(&v[0]) as u32), lua_int(&v[1]))
} else {
(None, v.first().map(lua_int).unwrap_or(0))
};
let c = check_char(c, 0x10_FFFF, name)?;
let code = read_code(lua, kind, table, c)?;
if parts {
return Ok(code_parts(kind, code)
.into_iter()
.map(Value::Integer)
.collect());
}
Ok(MultiValue::from_iter([code_value(lua, kind, code)?]))
}
fn set_code(lua: &Lua, kind: CodeT, name: &str, args: MultiValue) -> mlua::Result<()> {
let (global, v) = split_global(args);
let (table, c, val) = if v.len() >= 3 && matches!(kind, CodeT::Cat) {
(Some(lua_int(&v[0]) as u32), lua_int(&v[1]), &v[2])
} else if v.len() >= 2 {
(None, lua_int(&v[0]), &v[1])
} else {
return Err(mlua::Error::runtime("character and value expected"));
};
let c = check_char(c, 0x10_FFFF, name)?;
let val = match val {
Value::Table(t) => code_from_parts(kind, t)?,
v => {
let n = code_int(v, name)?;
match kind {
CodeT::Cat => i64::from(check_char(n, 15, name)?),
CodeT::Code(CodeKind::Lc | CodeKind::Uc) => {
i64::from(check_char(n, 0x10_FFFF, name)?)
}
_ => n,
}
}
};
with_engine(lua, |e| {
let loc = code_loc(e, kind, table, c);
e.define(global, loc, Val::Int(val));
})
}
fn code_table(lua: &Lua, kind: CodeT, name: &'static str) -> mlua::Result<Table> {
let t = lua.create_table()?;
let mt = lua.create_table()?;
mt.set(
"__index",
lua.create_function(move |lua, (_t, k): (Table, i64)| {
let code = read_code(lua, kind, None, k as u32)?;
code_value(lua, kind, code)
})?,
)?;
mt.set(
"__newindex",
lua.create_function(move |lua, (_t, k, v): (Table, Value, Value)| {
set_code(
lua,
kind,
&format!("set{name}"),
MultiValue::from_iter([k, v]),
)
})?,
)?;
t.set_metatable(Some(mt))?;
Ok(t)
}
fn get_param(lua: &Lua, name: &str, all: Option<bool>) -> mlua::Result<MultiValue> {
let fixed: Option<Value> = match name {
"luatexversion" => Some(Value::Integer(122)),
"luatexrevision" => Some(Value::String(lua.create_string("0")?)),
"eTeXversion" => Some(Value::Integer(2)),
"eTeXminorversion" => Some(Value::Integer(6)),
"eTeXrevision" => Some(Value::String(lua.create_string(".6")?)),
"inputlineno" => Some(Value::Integer(with_engine(lua, |e| e.input.line())?.into())),
"currentgrouplevel" => Some(Value::Integer(with_engine(lua, |e| {
i64::from(e.save.cur_level) - 1
})?)),
"currentgrouptype" => Some(Value::Integer(with_engine(lua, |e| {
e.save.cur_group as i64
})?)),
"jobname" => {
let j = with_engine(lua, |e| e.job_name.clone().unwrap_or_default())?;
Some(Value::String(lua.create_string(&j)?))
}
"randomseed" => Some(Value::Integer(with_engine(lua, |e| e.random_seed)?.into())),
"normaldeviate" => {
let v = with_engine(lua, |e| e.randoms.norm_rand())?;
Some(Value::String(lua.create_string(v.to_string())?))
}
"formatname" => {
let f = with_engine(lua, |e| e.format_name.clone())?;
Some(Value::String(lua.create_string(&f)?))
}
_ => None,
};
if let Some(v) = fixed {
return Ok(MultiValue::from_iter([v]));
}
let Some(p) = retex_engine::primitives::lookup(name.as_bytes()) else {
return Ok(MultiValue::from_iter([Value::String(
lua.create_string(name)?,
)]));
};
let v = with_engine(lua, |e| match p.cmd {
Cmd::AssignInt => Some(Val::Int(e.eqtb.int(p.chr).into())),
Cmd::AssignDimen => Some(Val::Int(e.eqtb.dimen(p.chr).into())),
Cmd::AssignGlue => Some(Val::Glue(e.eqtb.glue(p.chr))),
Cmd::AssignMuGlue => Some(Val::Glue(e.eqtb.glue(p.chr + MU_GLUE_OFFSET))),
Cmd::AssignToks => Some(Val::Toks(e.eqtb.toks_param(p.chr).clone())),
Cmd::AssignDirection => Some(Val::Int(e.dir_param(p.chr).into())),
Cmd::SetPageInt => Some(Val::Int(
match p.chr {
0 => e.dead_cycles,
1 => e.insert_penalties,
_ => e.err.interaction as i32,
}
.into(),
)),
_ => None,
})?;
if p.cmd == Cmd::AssignDir {
let d = with_engine(lua, |e| e.dir_param(p.chr))?;
let name = retex_engine::showbox::DIR_NAMES[d.rem_euclid(4) as usize];
return Ok(MultiValue::from_iter([Value::String(
lua.create_string(name)?,
)]));
}
let Some(v) = v else {
return Ok(MultiValue::new());
};
if let (Val::Glue(g), Some(false)) = (&v, all) {
return Ok(MultiValue::from_iter([Value::Integer(g.width.into())]));
}
if let Val::Glue(g) = &v
&& all == Some(true)
{
return Ok(MultiValue::from_iter([
Value::Integer(g.width.into()),
Value::Integer(g.stretch.into()),
Value::Integer(g.shrink.into()),
Value::Integer(g.stretch_order.into()),
Value::Integer(g.shrink_order.into()),
]));
}
Ok(MultiValue::from_iter([val_to_lua(lua, v)?]))
}
fn set_param(lua: &Lua, global: bool, name: &str, v: Value) -> mlua::Result<()> {
let Some(p) = retex_engine::primitives::lookup(name.as_bytes()) else {
return Ok(());
};
let int = |v: &Value| match v {
Value::Integer(n) => *n,
Value::Number(n) => n.round() as i64,
Value::Boolean(b) => i64::from(*b),
_ => 0,
};
if matches!(p.cmd, Cmd::AssignDir | Cmd::AssignDirection) {
let d = match &v {
Value::String(s) => retex_engine::showbox::DIR_NAMES
.iter()
.position(|n| n.as_bytes() == s.as_bytes().as_ref())
.unwrap_or(0) as i32,
other => int(other).clamp(0, 3) as i32,
};
return with_engine(lua, |e| {
let prev = e.dir_param(p.chr);
e.assign_dir_param(global, p.chr, d);
if p.chr == retex_engine::primitives_gen::assign_dir::TEXTDIR {
e.text_dir_assigned(d, prev);
}
});
}
let (loc, val) = match p.cmd {
Cmd::AssignInt => (Loc::IntPar(p.chr), Val::Int(int(&v))),
Cmd::AssignDimen => {
let d = match &v {
Value::String(s) => {
let s = s.to_str()?.to_owned();
with_engine(lua, |e| e.dimen_from_string(&s))?
}
other => int(other),
};
(Loc::DimenPar(p.chr), Val::Int(d))
}
Cmd::AssignGlue => (Loc::GluePar(p.chr), Val::Glue(lua_to_glue(lua, &v)?)),
Cmd::AssignMuGlue => (
Loc::GluePar(p.chr + MU_GLUE_OFFSET),
Val::Glue(lua_to_glue(lua, &v)?),
),
Cmd::AssignToks => (Loc::ToksPar(p.chr), lua_to_val(lua, Reg::Toks, &v)?),
_ => {
return Err(mlua::Error::runtime(format!(
"tex.set: '{name}' is not a parameter"
)));
}
};
with_engine(lua, |e| {
e.define(global, loc, val);
if let Loc::IntPar(i) = loc {
e.int_param_changed(i);
}
})
}
pub(crate) const MATH_STYLE_NAMES: [&str; 8] = [
"display",
"crampeddisplay",
"text",
"crampedtext",
"script",
"crampedscript",
"scriptscript",
"crampedscriptscript",
];
fn line_break(lua: &Lua, args: MultiValue) -> mlua::Result<(Value, Table)> {
let mut args = args.into_iter();
let head = match args.next() {
Some(Value::UserData(u)) if u.is::<crate::nodelib::LNode>() => {
retex_engine::nodes::NodeRef(u.borrow::<crate::nodelib::LNode>()?.0)
}
Some(v) => return Err(not_a_node(lua, &v)),
None => {
let msg = "lua <node> expected, not an object with type no value";
return Err(
match with_engine(lua, |e| e.luatex_fatal("node lib", msg)) {
Err(e) => e,
},
);
}
};
let mut p = with_engine(lua, |e| {
let w = e
.eqtb
.int(retex_engine::primitives_gen::assign_int::WIDOWPENALTY);
e.par_params(w, false)
})?;
if let Some(Value::Table(t)) = args.next() {
let int = |key: &str| -> mlua::Result<Option<i32>> {
Ok(match t.raw_get::<Value>(key)? {
Value::Integer(n) => Some(n as i32),
Value::Number(x) => Some(x.round() as i32),
_ => None,
})
};
let penalties = |key: &str, int_val: &mut i32, arr: &mut Option<Rc<[i32]>>| {
match t.raw_get::<Value>(key)? {
Value::Integer(n) => {
*int_val = n as i32;
*arr = None;
}
Value::Number(x) => {
*int_val = x.round() as i32;
*arr = None;
}
Value::Table(a) => {
let v: Vec<i32> = a
.sequence_values::<f64>()
.map(|x| x.map(|x| x.round() as i32))
.collect::<mlua::Result<_>>()?;
*int_val = 0;
*arr = if v.is_empty() { None } else { Some(v.into()) };
}
_ => {}
}
Ok::<(), mlua::Error>(())
};
let ints: [(&str, &mut i32); 18] = [
("pretolerance", &mut p.pretolerance),
("tolerance", &mut p.tolerance),
("looseness", &mut p.looseness),
("adjdemerits", &mut p.adj_demerits),
("linepenalty", &mut p.line_penalty),
("lastlinefit", &mut p.last_line_fit),
("doublehyphendemerits", &mut p.double_hyphen_demerits),
("finalhyphendemerits", &mut p.final_hyphen_demerits),
("hangafter", &mut p.hang_after),
("hangindent", &mut p.hang_indent),
("hsize", &mut p.hsize),
("emergencystretch", &mut p.emergency_stretch),
("brokenpenalty", &mut p.broken_penalty),
("pdfprotrudechars", &mut p.protrude_chars),
("pdfadjustspacing", &mut p.adjust_spacing),
("tracingparagraphs", &mut p.tracing_paragraphs),
("hyphenpenalty", &mut 0),
("exhyphenpenalty", &mut 0),
];
for (key, dst) in ints {
if let Some(v) = int(key)? {
*dst = v;
}
}
match t.raw_get::<Value>("interlinepenalty")? {
Value::Table(_) => p.interline_penalty = 0,
_ => {
if let Some(v) = int("interlinepenalty")? {
p.interline_penalty = v;
}
}
}
penalties("clubpenalty", &mut p.club_penalty, &mut p.club_penalties)?;
penalties("widowpenalty", &mut p.widow_penalty, &mut p.widow_penalties)?;
for (key, dst) in [
("leftskip", &mut p.left_skip),
("rightskip", &mut p.right_skip),
] {
match t.raw_get::<Value>(key)? {
Value::Nil => {}
Value::UserData(u) if u.is::<crate::nodelib::LNode>() => {
let n = retex_engine::nodes::NodeRef(u.borrow::<crate::nodelib::LNode>()?.0);
*dst = with_engine(lua, |e| e.nodes.glue_spec(n))?;
}
v => return Err(not_a_node(lua, &v)),
}
}
if let Value::String(s) = t.raw_get::<Value>("pardir")? {
p.par_dir = match s.as_bytes().as_ref() {
b"TLT" => 0,
b"TRT" => 1,
b"LTL" => 2,
b"RTT" => 3,
_ => {
return Err(mlua::Error::runtime(format!(
"Bad direction specifier {}",
s.to_string_lossy()
)));
}
};
}
if let Value::Table(shape) = t.raw_get::<Value>("parshape")? {
let mut v: Vec<i32> = Vec::new();
for line in shape.sequence_values::<Value>() {
if let Value::Table(line) = line? {
for i in 1..=2 {
v.push(match line.raw_get::<Value>(i)? {
Value::Integer(n) => n as i32,
Value::Number(x) => x.round() as i32,
_ => 0,
});
}
}
}
p.par_shape = if v.is_empty() { None } else { Some(v.into()) };
}
}
let (lines, info) = with_engine(lua, |e| e.lua_line_break(head, &p))?;
let t = lua.create_table()?;
t.set("prevdepth", info.prev_depth)?;
t.set("prevgraf", info.prev_graf)?;
t.set("looseness", info.looseness)?;
t.set("demerits", info.demerits)?;
Ok((crate::nodelib::wrap(lua, lines, false)?, t))
}
fn get_math(lua: &Lua, (name, style): (String, Value)) -> mlua::Result<MultiValue> {
let param = format!("Umath{name}");
let m = retex_engine::primitives_gen::PRIMITIVES
.iter()
.find(|p| p.cmd == Cmd::SetMathParam && p.name == param)
.map(|p| p.chr)
.ok_or_else(|| {
mlua::Error::runtime(format!(
"bad argument #1 to 'getmath' (invalid option '{name}')"
))
})?;
let s = match &style {
Value::String(s) => {
let s = s.to_str()?.to_string();
MATH_STYLE_NAMES
.iter()
.position(|n| *n == s)
.ok_or_else(|| {
mlua::Error::runtime(format!(
"bad argument #2 to 'getmath' (invalid option '{s}')"
))
})?
}
other => {
let s = lua_int(other);
return Err(mlua::Error::runtime(format!(
"bad argument #2 to 'getmath' (invalid option '{s}')"
)));
}
} as u32;
let (v, g) = with_engine(lua, |e| {
let v = e.math_param_value(m, s);
(v, e.math_param_glue(v))
})?;
if retex_engine::codes::math_param_is_glue(m) {
if v == retex_engine::codes::UNDEFINED_MATH_PARAM {
return Ok(MultiValue::from_iter([Value::Nil]));
}
return Ok(MultiValue::from_iter([
Value::Integer(g.width.into()),
Value::Integer(g.stretch.into()),
Value::Integer(g.shrink.into()),
mlua::IntoLua::into_lua(g.stretch_order, lua)?,
mlua::IntoLua::into_lua(g.shrink_order, lua)?,
]));
}
Ok(MultiValue::from_iter([Value::Integer(v.into())]))
}
fn install_more(lua: &Lua, tex: &Table) -> mlua::Result<()> {
use retex_engine::nodes::NodeRef;
let int_at = |v: &[Value], i: usize| -> i32 {
match v.get(i) {
Some(Value::Integer(n)) => *n as i32,
Some(Value::Number(x)) => x.round() as i32,
_ => 0,
}
};
for (set, get, kind) in [
("setglue", "getglue", Reg::Skip),
("setmuglue", "getmuglue", Reg::MuSkip),
] {
tex.set(
set,
lua.create_function(move |lua, args: MultiValue| {
let (global, v) = split_global(args);
let Some(k) = v.first() else {
return Err(mlua::Error::runtime("register index expected"));
};
let g = Glue {
width: int_at(&v, 1),
stretch: int_at(&v, 2),
shrink: int_at(&v, 3),
stretch_order: int_at(&v, 4).clamp(0, 4) as u8,
shrink_order: int_at(&v, 5).clamp(0, 4) as u8,
};
if let Some(loc) = glue_param_loc(k, kind) {
return with_engine(lua, |e| e.define(global, loc, Val::Glue(g)));
}
let n = reg_index(lua, kind, k, true)?;
with_engine(lua, |e| e.define(global, reg_loc(kind, n), Val::Glue(g)))
})?,
)?;
tex.set(
get,
lua.create_function(move |lua, k: Value| {
let loc = match glue_param_loc(&k, kind) {
Some(loc) => loc,
None => reg_loc(kind, reg_index(lua, kind, &k, false)?),
};
let g = match with_engine(lua, |e| e.eqtb.get(loc))? {
Val::Glue(g) => g,
_ => Glue::ZERO,
};
Ok((
g.width,
g.stretch,
g.shrink,
i64::from(g.stretch_order),
i64::from(g.shrink_order),
))
})?,
)?;
}
for (name, cmd) in [
("iscount", Cmd::AssignInt),
("isdimen", Cmd::AssignDimen),
("isskip", Cmd::AssignGlue),
("isglue", Cmd::AssignGlue),
("ismuskip", Cmd::AssignMuGlue),
("ismuglue", Cmd::AssignMuGlue),
("istoks", Cmd::AssignToks),
("isbox", Cmd::CharGiven),
] {
tex.set(
name,
lua.create_function(move |lua, s: mlua::String| {
let name = s.as_bytes().to_vec();
let r = with_engine(lua, |e| {
let cs = e.hash.lookup(&name)?;
let m = e.eqtb.meaning(cs);
if m.cmd != cmd || m.mac.is_some() {
return None;
}
if cmd == Cmd::CharGiven {
return Some(m.chr);
}
m.chr.checked_sub(REG_OFFSET)
})?;
Ok(match r {
Some(n) => Value::Integer(i64::from(n)),
None => Value::Boolean(false),
})
})?,
)?;
}
tex.set(
"romannumeral",
lua.create_function(|lua, n: i64| {
let s = with_engine(lua, |e| e.print_to_string(|e| e.printer.print_roman_int(n)))?;
lua.create_string(&s)
})?,
)?;
tex.set(
"number",
lua.create_function(|lua, n: i64| lua.create_string(n.to_string()))?,
)?;
tex.set(
"fontname",
lua.create_function(|lua, f: i64| {
let s = with_engine(lua, |e| {
let f = u32::try_from(f)
.ok()
.filter(|&f| (f as usize) < e.fonts.fonts.len());
f.map(|f| e.print_to_string(|e| e.print_font_name_and_size(f)))
})?;
match s {
Some(s) => Ok(Value::String(lua.create_string(&s)?)),
None => Ok(Value::Nil),
}
})?,
)?;
tex.set(
"fontidentifier",
lua.create_function(|lua, f: i64| {
let s = with_engine(lua, |e| {
let f = u32::try_from(f)
.ok()
.filter(|&f| (f as usize) < e.fonts.fonts.len());
f.map(|f| e.print_to_string(|e| e.print_font_id(f)))
})?;
match s {
Some(s) => Ok(Value::String(lua.create_string(&s)?)),
None => Ok(Value::Nil),
}
})?,
)?;
tex.set(
"badness",
lua.create_function(|_, (t, s): (i64, i64)| {
let (t, s) = (t.max(0), s.max(0));
Ok(if t == 0 {
0
} else if s <= 0 {
10000
} else {
let r = if t <= 7_230_584 {
(t * 297) / s
} else if s >= 1_663_497 {
t / (s / 297)
} else {
t
};
if r > 1290 {
10000
} else {
(r * r * r + 0x20000) / 0x40000
}
})
})?,
)?;
tex.set(
"getlocallevel",
lua.create_function(|lua, ()| with_engine(lua, |e| i64::from(e.local_level)))?,
)?;
tex.set(
"getpagestate",
lua.create_function(|lua, ()| with_engine(lua, |e| e.page.contents as i64))?,
)?;
tex.set(
"getmark",
lua.create_function(move |lua, args: MultiValue| {
let v: Vec<Value> = args.into_iter().collect();
if v.is_empty() {
let m = with_engine(lua, |e| e.marks.classes.keys().copied().max().unwrap_or(0))?;
return Ok(Value::Integer(i64::from(m)));
}
let code = match v.first() {
Some(Value::String(s)) => match &*s.to_str()? {
"top" => 0,
"first" => 1,
"bottom" => 2,
"splitfirst" => 3,
"splitbottom" => 4,
_ => return Ok(Value::Nil),
},
_ => return Ok(Value::Nil),
};
let class = int_at(&v, 1).max(0) as u32;
let s = with_engine(lua, |e| {
let t = e.marks.get(class, code)?;
Some(e.print_to_string(|e| e.show_token_list(&t, 0, t.len(), i64::MAX)))
})?;
match s {
Some(s) => Ok(Value::String(lua.create_string(&s)?)),
None => Ok(Value::Nil),
}
})?,
)?;
tex.set(
"getlist",
lua.create_function(|lua, name: String| get_list(lua, &name))?,
)?;
tex.set(
"setlist",
lua.create_function(|lua, (name, v): (String, Value)| set_list(lua, &name, v))?,
)?;
let lists = lua.create_table()?;
let mt = lua.create_table()?;
mt.set(
"__index",
lua.create_function(|lua, (_t, name): (Table, String)| get_list(lua, &name))?,
)?;
mt.set(
"__newindex",
lua.create_function(|lua, (_t, name, v): (Table, String, Value)| set_list(lua, &name, v))?,
)?;
lists.set_metatable(Some(mt))?;
tex.set("lists", lists)?;
tex.set(
"splitbox",
lua.create_function(|lua, (n, h, _mode): (i64, i64, Option<String>)| {
let n = reg_index(lua, Reg::Box, &Value::Integer(n), true)?;
let b = with_engine(lua, |e| e.vsplit(n, h as i32))?;
if b.is_null() {
return Ok(Value::Nil);
}
crate::nodelib::wrap(lua, NodeRef(b.0), false)
})?,
)?;
tex.set(
"definefont",
lua.create_function(move |lua, args: MultiValue| {
let mut v: Vec<Value> = args.into_iter().collect();
let global = matches!(v.first(), Some(Value::Boolean(true)));
if matches!(v.first(), Some(Value::Boolean(_))) {
v.remove(0);
}
let Some(Value::String(name)) = v.first() else {
return Ok(());
};
let name = name.as_bytes().to_vec();
let f = int_at(&v, 1).max(0) as u32;
with_engine(lua, |e| {
let f = if (f as usize) < e.fonts.fonts.len() {
f
} else {
0
};
let cs = e.hash.lookup_or_insert(&name);
e.define(
global,
Loc::Cs(cs),
Val::Meaning(retex_engine::eqtb::Meaning::prim(Cmd::SetFont, f)),
);
})
})?,
)?;
Ok(())
}
fn get_list(lua: &Lua, name: &str) -> mlua::Result<Value> {
use retex_engine::nodes::NodeRef;
enum L {
Node(NodeRef),
Int(i32),
None,
}
let r = with_engine(lua, |e| match name {
"page_head" => {
let p = e.nodes.next(e.page.page_head);
L::Node(if p == e.page.page_head {
NodeRef::NULL
} else {
p
})
}
"contrib_head" => L::Node(e.nodes.next(e.page.contrib_head)),
"align_head" => L::Node(e.align_preamble_list()),
"page_disc" => L::Node(e.ts.disc_ptr[2]),
"split_disc" => L::Node(e.ts.disc_ptr[3]),
"adjust_head" => L::Node(e.nodes.next(e.ts.adjust_head)),
"pre_adjust_head" => L::Node(e.nodes.next(e.ts.pre_adjust_head)),
"best_page_break" => L::Node(e.page.best_break),
"least_page_cost" => L::Int(e.page.least_cost),
"best_size" => L::Int(e.page.best_size),
_ => L::None,
})?;
Ok(match r {
L::Node(p) if !p.is_null() => crate::nodelib::wrap(lua, p, false)?,
L::Int(n) => Value::Integer(i64::from(n)),
_ => Value::Nil,
})
}
fn set_list(lua: &Lua, name: &str, v: Value) -> mlua::Result<()> {
use retex_engine::nodes::NodeRef;
let is_list = matches!(
name,
"page_head" | "contrib_head" | "adjust_head" | "pre_adjust_head" | "best_page_break"
);
if is_list {
let p = match &v {
Value::Nil => NodeRef::NULL,
Value::UserData(u) if u.is::<crate::nodelib::LNode>() => {
NodeRef(u.borrow::<crate::nodelib::LNode>()?.0)
}
other => return Err(not_a_node(lua, other)),
};
return with_engine(lua, |e| {
let head = match name {
"page_head" => e.page.page_head,
"contrib_head" => e.page.contrib_head,
"adjust_head" => e.ts.adjust_head,
"pre_adjust_head" => e.ts.pre_adjust_head,
_ => {
e.page.best_break = p;
return;
}
};
e.nodes.set_next(head, NodeRef::NULL);
let tail = if p.is_null() {
head
} else {
e.nodes.link(head, p);
e.nodes.tail_of(p)
};
match name {
"page_head" => e.page.page_tail = tail,
"contrib_head" => {
if let Some(l) = e.nest.first_mut() {
l.tail = tail;
}
}
_ => {}
}
});
}
let n = match v {
Value::Integer(n) => n as i32,
Value::Number(x) => x.round() as i32,
_ => return Ok(()),
};
with_engine(lua, |e| match name {
"least_page_cost" => e.page.least_cost = n,
"best_size" => e.page.best_size = n,
_ => {}
})
}