retex-lua 1.0.0

Embedded Lua and the LuaTeX Lua API for retex
Documentation
//! The `lang` library (LuaTeX manual ยง5.8).

use mlua::{Lua, MetaMethod, MultiValue, UserData, UserDataMethods, Value};

use crate::with_engine;

/// A language object: a reference to `\language` number `0`.
#[derive(Clone, Copy, Debug)]
pub struct LLang(pub u32);

impl UserData for LLang {
    fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
        m.add_meta_method(MetaMethod::ToString, |_, this, ()| {
            Ok(format!("<language {}>", this.0))
        });
    }
}

fn lang_of(v: &Value) -> Option<u32> {
    match v {
        Value::UserData(u) => u.borrow::<LLang>().ok().map(|l| l.0),
        Value::Integer(n) => Some(*n as u32),
        _ => None,
    }
}

/// Installs `lang`.
pub fn install(lua: &Lua) -> mlua::Result<()> {
    let t = lua.create_table()?;
    t.set(
        "new",
        lua.create_function(|lua, n: Option<i64>| {
            let id = match n {
                Some(n) => n as u32,
                None => with_engine(lua, |e| {
                    let id = e.languages.len() as u32;
                    e.language_mut(id);
                    id
                })?,
            };
            Ok(LLang(id))
        })?,
    )?;
    t.set(
        "id",
        lua.create_function(|_, v: Value| Ok(lang_of(&v).map(i64::from)))?,
    )?;
    t.set(
        "patterns",
        lua.create_function(|lua, (l, text): (Value, Option<String>)| {
            let Some(n) = lang_of(&l) else {
                return Ok(Value::Nil);
            };
            match text {
                Some(s) => {
                    with_engine(lua, |e| e.language_mut(n).add_patterns(&s))?;
                    Ok(Value::Nil)
                }
                None => {
                    let s = with_engine(lua, |e| e.language(n).map(|l| l.patterns_text()))?;
                    match s {
                        Some(s) => Ok(Value::String(lua.create_string(&s)?)),
                        None => Ok(Value::Nil),
                    }
                }
            }
        })?,
    )?;
    t.set(
        "clear_patterns",
        lua.create_function(|lua, l: Value| {
            if let Some(n) = lang_of(&l) {
                with_engine(lua, |e| e.language_mut(n).clear_patterns())?;
            }
            Ok(())
        })?,
    )?;
    t.set(
        "hyphenation",
        lua.create_function(|lua, (l, text): (Value, Option<String>)| {
            let Some(n) = lang_of(&l) else {
                return Ok(Value::Nil);
            };
            match text {
                Some(s) => {
                    with_engine(lua, |e| {
                        let _ = e.language_mut(n).add_exceptions(&s);
                    })?;
                    Ok(Value::Nil)
                }
                None => {
                    let s = with_engine(lua, |e| e.language(n).map(|l| l.exceptions_text()))?;
                    match s {
                        Some(s) => Ok(Value::String(lua.create_string(&s)?)),
                        None => Ok(Value::Nil),
                    }
                }
            }
        })?,
    )?;
    t.set(
        "clear_hyphenation",
        lua.create_function(|lua, l: Value| {
            if let Some(n) = lang_of(&l) {
                with_engine(lua, |e| e.language_mut(n).clear_exceptions())?;
            }
            Ok(())
        })?,
    )?;
    t.set(
        "clean",
        lua.create_function(|_, args: MultiValue| {
            let v: Vec<Value> = args.into_iter().collect();
            let s = match v.last() {
                Some(Value::String(s)) => s.to_str()?.to_owned(),
                _ => return Ok(None),
            };
            Ok(retex_hyph::exception::clean(&s))
        })?,
    )?;
    for (name, which) in [
        ("prehyphenchar", 0),
        ("posthyphenchar", 1),
        ("preexhyphenchar", 2),
        ("postexhyphenchar", 3),
        ("hyphenationmin", 4),
    ] {
        t.set(
            name,
            lua.create_function(move |lua, (l, v): (Value, Option<i64>)| {
                let Some(n) = lang_of(&l) else {
                    return Ok(None);
                };
                with_engine(lua, |e| {
                    let s = &mut e.language_mut(n).settings;
                    let field = match which {
                        0 => &mut s.pre_hyphen_char,
                        1 => &mut s.post_hyphen_char,
                        2 => &mut s.pre_ex_hyphen_char,
                        3 => &mut s.post_ex_hyphen_char,
                        _ => &mut s.hyphenation_min,
                    };
                    match v {
                        Some(x) => {
                            *field = x as i32;
                            None
                        }
                        None => Some(i64::from(*field)),
                    }
                })
            })?,
        )?;
    }
    t.set(
        "sethjcode",
        lua.create_function(|lua, (l, c, v): (Value, i64, i64)| {
            if let Some(n) = lang_of(&l) {
                with_engine(lua, |e| e.language_mut(n).set_hj_code(c as u32, v as u32))?;
            }
            Ok(())
        })?,
    )?;
    t.set(
        "gethjcode",
        lua.create_function(|lua, (l, c): (Value, i64)| {
            let Some(n) = lang_of(&l) else {
                return Ok(0);
            };
            with_engine(lua, |e| {
                e.language(n).map_or(0, |l| i64::from(l.hj_code(c as u32)))
            })
        })?,
    )?;
    t.set(
        "hyphenate",
        // The list must be a node (observed: lang.hyphenate(nil) is
        // LuaTeX's fatal "lua <node> expected").
        lua.create_function(|lua, args: MultiValue| {
            let first = args.front().cloned().unwrap_or(Value::Nil);
            let Some(h) =
                crate::nodelib::to_ref(&first).filter(|_| !matches!(first, Value::Integer(_)))
            else {
                return Err(crate::texlib::not_a_node(lua, &first));
            };
            with_engine(lua, |e| {
                e.lua_node_pass(retex_engine::nodeproc::NodePass::Hyphenate, h)
            })?;
            Ok(true)
        })?,
    )?;
    lua.globals().set("lang", t)?;
    Ok(())
}