retex-lua 0.9.9

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))?))
}

/// `tex.lists[key]`: the list after one of the engine's list heads
/// (observed: nil when it is empty; `align_head` holds the current
/// preamble's tabskip glue and align records).
fn list(lua: &Lua, key: &str) -> mlua::Result<Value> {
    let p = with_engine(lua, |e| {
        let after = |e: &retex_engine::Engine, h: NodeRef| {
            if h.is_null() {
                NodeRef::NULL
            } else {
                e.nodes.next(h)
            }
        };
        match key {
            "align_head" => e.align_preamble_list(),
            "contrib_head" => after(e, e.page.contrib_head),
            "page_head" => {
                let p = after(e, e.page.page_head);
                if p == e.page.page_head {
                    NodeRef::NULL
                } else {
                    p
                }
            }
            "adjust_head" => after(e, e.ts.adjust_head),
            "pre_adjust_head" => after(e, e.ts.pre_adjust_head),
            "page_disc" => e.ts.disc_ptr[2],
            "split_disc" => e.ts.disc_ptr[3],
            _ => NodeRef::NULL,
        }
    })?;
    if p.is_null() {
        Ok(Value::Nil)
    } else {
        wrap(lua, p, false)
    }
}

/// Installs `tex.nest`, `tex.getnest`, `tex.lists` and
/// `tex.getmodevalues`.
pub fn install(lua: &Lua, tex: &Table) -> mlua::Result<()> {
    let lists = lua.create_table()?;
    let mt = lua.create_table()?;
    mt.set(
        "__index",
        lua.create_function(|lua, (_t, k): (Value, String)| list(lua, &k))?,
    )?;
    mt.set(
        "__newindex",
        lua.create_function(|_, (_t, _k, _v): (Value, Value, Value)| Ok(()))?,
    )?;
    lists.set_metatable(Some(mt))?;
    tex.set("lists", lists)?;
    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(())
}