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_plain_errors, push_print, with_engine};
pub fn install(lua: &Lua) -> mlua::Result<()> {
let tex = lua.create_table()?;
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 || 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(
"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 as u8,
Some(Value::Number(n)) => n as u8,
_ => 12,
};
for s in strings_of(lua, it.collect())? {
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)?)?;
tex.set(
format!("set{name}"),
lua.create_function(move |lua, args: MultiValue| set_code(lua, kind, args))?,
)?;
tex.set(
format!("get{name}"),
lua.create_function(move |lua, args: MultiValue| get_code(lua, kind, args))?,
)?;
}
tex.set(
"get",
lua.create_function(|lua, (name, all): (String, Option<bool>)| {
get_param(lua, &name, all.unwrap_or(false))
})?,
)?;
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)) => {
if let Some(mut ps) = lua.app_data_mut::<crate::PrintStack>() {
ps.0.push(Vec::new());
}
let r = f.call::<()>(());
let items = lua
.app_data_mut::<crate::PrintStack>()
.and_then(|mut ps| ps.0.pop())
.unwrap_or_default();
r?;
with_engine(lua, |e| {
e.push_local_marker();
e.push_lua_output(items);
e.run_local_loop();
})
}
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, false))?,
)?;
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);
}
}
for s in strings_of(lua, v)? {
push_print(
lua,
LuaItem::Text {
text: s,
kind,
catmode,
},
);
}
Ok(())
}
#[derive(Clone, Copy)]
enum Reg {
Count,
Dimen,
Skip,
MuSkip,
Toks,
Attr,
Box,
}
fn reg_index(lua: &Lua, kind: Reg, k: &Value) -> mlua::Result<u32> {
match k {
Value::Integer(n) => Ok(*n as u32),
Value::Number(n) => Ok(*n as u32),
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 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)?;
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(v: &Value) -> Glue {
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),
},
_ => Glue::ZERO,
}
}
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))?
}
_ => 0,
}),
Reg::Skip | Reg::MuSkip => Val::Glue(lua_to_glue(v)),
Reg::Toks => {
let s = match v {
Value::String(s) => s.as_bytes().to_vec(),
_ => Vec::new(),
};
let toks = retex_engine::the::str_toks(&s);
Val::Toks(Rc::from(toks))
}
Reg::Box => Val::Node(retex_engine::nodes::NodeRef(match v {
Value::Integer(n) => *n as u32,
Value::UserData(u) => u.borrow::<crate::nodelib::LNode>().map_or(0, |n| n.0),
_ => 0,
})),
})
}
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])?;
let val = lua_to_val(lua, kind, &v[1])?;
with_engine(lua, |e| 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)?;
let val = lua_to_val(lua, kind, &v)?;
with_engine(lua, |e| e.define(false, reg_loc(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 get_code(lua: &Lua, kind: CodeT, args: MultiValue) -> mlua::Result<i64> {
let v: Vec<Value> = args.into_iter().collect();
let num = |v: &Value| match v {
Value::Integer(n) => *n,
Value::Number(n) => *n as i64,
_ => 0,
};
let (table, c) = if v.len() >= 2 && matches!(kind, CodeT::Cat) {
(Some(num(&v[0]) as u32), num(&v[1]) as u32)
} else {
(None, v.first().map(num).unwrap_or(0) as u32)
};
with_engine(lua, |e| match e.eqtb.get(code_loc(e, kind, table, c)) {
Val::Int(i) => i,
_ => 0,
})
}
fn set_code(lua: &Lua, kind: CodeT, args: MultiValue) -> mlua::Result<()> {
let (global, v) = split_global(args);
let num = |v: &Value| match v {
Value::Integer(n) => *n,
Value::Number(n) => *n as i64,
_ => 0,
};
let (table, c, val) = if v.len() >= 3 && matches!(kind, CodeT::Cat) {
(Some(num(&v[0]) as u32), num(&v[1]) as u32, num(&v[2]))
} else if v.len() >= 2 {
(None, num(&v[0]) as u32, num(&v[1]))
} else {
return Err(mlua::Error::runtime("character and value expected"));
};
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) -> 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)| {
with_engine(lua, |e| {
match e.eqtb.get(code_loc(e, kind, None, k as u32)) {
Val::Int(i) => i,
_ => 0,
}
})
})?,
)?;
mt.set(
"__newindex",
lua.create_function(move |lua, (_t, k, v): (Table, i64, i64)| {
with_engine(lua, |e| {
let loc = code_loc(e, kind, None, k as u32);
e.define(false, loc, Val::Int(v));
})
})?,
)?;
t.set_metatable(Some(mt))?;
Ok(t)
}
fn get_param(lua: &Lua, name: &str, all: 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::new());
};
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::AssignDir | 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,
})?;
let Some(v) = v else {
return Ok(MultiValue::new());
};
if let Val::Glue(g) = &v
&& all
{
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 Err(mlua::Error::runtime(format!(
"tex.set: unknown parameter '{name}'"
)));
};
let int = |v: &Value| match v {
Value::Integer(n) => *n,
Value::Number(n) => n.round() as i64,
Value::Boolean(b) => i64::from(*b),
_ => 0,
};
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(&v))),
Cmd::AssignMuGlue => (
Loc::GluePar(p.chr + MU_GLUE_OFFSET),
Val::Glue(lua_to_glue(&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);
}
})
}