retex-lua 1.2.0

Embedded Lua and the LuaTeX Lua API for retex
Documentation
//! `tex.nest`, `tex.getnest` and `tex.getmodevalues` (LuaTeX manual
//! ยง10.3.12): the semantic nest from Lua.
//!
//! Observed in LuaTeX: the mode numbers are 1 (vertical), 134
//! (horizontal) and 267 (math), negative for the inner modes, 0 for no
//! mode. `tex.nest.ptr` is the current level, `tex.nest.top` and
//! `tex.nest[i]` (0 to `ptr`) its entries; `head` is the list's temporary
//! head node, `tail` its last node (the head for an empty list). `tail`,
//! `prevgraf`, `prevdepth`, `spacefactor` and `modeline` can be set.

use mlua::{Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
use retex_engine::maincontrol::Mode;
use retex_engine::nodes::NodeRef;

use crate::nodelib::{to_ref, wrap};
use crate::with_engine;

/// LuaTeX's number for a mode.
pub fn mode_number(m: Mode) -> i64 {
    match m {
        Mode::None => 0,
        Mode::Vertical => 1,
        Mode::InternalVertical => -1,
        Mode::Horizontal => 134,
        Mode::RestrictedHorizontal => -134,
        Mode::DisplayMath => 267,
        Mode::Math => -267,
    }
}

/// An entry of the nest, by level.
#[derive(Clone, Copy)]
struct NestEntry(usize);

/// What a field of an entry is.
enum Field {
    Int(i64),
    Node(NodeRef),
    Nil,
}

impl UserData for NestEntry {
    fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
        m.add_meta_method(MetaMethod::Index, |lua, this, key: String| {
            let level = this.0;
            let f = with_engine(lua, |e| {
                let Some(l) = e.nest.get(level) else {
                    return Field::Nil;
                };
                match key.as_str() {
                    "mode" => Field::Int(mode_number(l.mode)),
                    "modeline" => Field::Int(i64::from(l.mode_line)),
                    "head" => Field::Node(l.head),
                    "tail" => Field::Node(l.tail),
                    "prevgraf" => Field::Int(i64::from(l.prev_graf)),
                    "prevdepth" => Field::Int(i64::from(l.prev_depth)),
                    "spacefactor" => Field::Int(i64::from(l.space_factor)),
                    "noad" | "incompleatnoad" => Field::Node(l.incompleat_noad),
                    "delimptr" => Field::Node(l.delim_ptr),
                    "mathstyle" => Field::Int(i64::from(l.math_style)),
                    _ => Field::Nil,
                }
            })?;
            match f {
                Field::Int(i) => Ok(Value::Integer(i)),
                Field::Node(p) => wrap(lua, p, false),
                Field::Nil => Ok(Value::Nil),
            }
        });
        m.add_meta_method(
            MetaMethod::NewIndex,
            |lua, this, (key, v): (String, Value)| {
                let level = this.0;
                let p = to_ref(&v);
                let n = match &v {
                    Value::Integer(n) => *n,
                    Value::Number(n) => *n as i64,
                    _ => 0,
                };
                with_engine(lua, |e| {
                    let Some(l) = e.nest.get_mut(level) else {
                        return;
                    };
                    match key.as_str() {
                        "tail" => {
                            if let Some(p) = p {
                                l.tail = p;
                            }
                        }
                        "head" => {
                            if let Some(p) = p {
                                l.head = p;
                            }
                        }
                        "prevgraf" => l.prev_graf = n as i32,
                        "prevdepth" => l.prev_depth = n as i32,
                        "spacefactor" => l.space_factor = n as i32,
                        "modeline" => l.mode_line = n as i32,
                        _ => {}
                    }
                })
            },
        );
    }
}

/// The entry for `key` (`"top"`, `"ptr"` or a level).
fn lookup(lua: &Lua, key: &Value) -> mlua::Result<Value> {
    let ptr = with_engine(lua, |e| e.nest.len() - 1)?;
    let level = match key {
        Value::String(s) if &*s.to_str()? == "ptr" => return Ok(Value::Integer(ptr as i64)),
        Value::String(s) if &*s.to_str()? == "top" => ptr,
        Value::Nil => ptr,
        Value::Integer(i) if (0..=ptr as i64).contains(i) => *i as usize,
        Value::Number(f) if (0.0..=ptr as f64).contains(f) => *f as usize,
        _ => return Ok(Value::Nil),
    };
    Ok(Value::UserData(lua.create_userdata(NestEntry(level))?))
}

/// Installs `tex.nest`, `tex.getnest` and `tex.getmodevalues`
/// (`tex.lists` is in [`crate::texlib`]).
pub fn install(lua: &Lua, tex: &Table) -> mlua::Result<()> {
    let nest = lua.create_table()?;
    let mt = lua.create_table()?;
    mt.set(
        "__index",
        lua.create_function(|lua, (_t, k): (Value, Value)| lookup(lua, &k))?,
    )?;
    nest.set_metatable(Some(mt))?;
    tex.set("nest", nest)?;
    tex.set(
        "getnest",
        lua.create_function(|lua, k: Value| lookup(lua, &k))?,
    )?;
    tex.set(
        "getmodevalues",
        lua.create_function(|lua, ()| {
            let t = lua.create_table()?;
            for (k, v) in [
                (0, "unset"),
                (1, "vertical"),
                (134, "horizontal"),
                (267, "math"),
            ] {
                t.set(k, v)?;
            }
            Ok(t)
        })?,
    )?;
    Ok(())
}