Skip to main content

retex_lua/
nodelib.rs

1//! The `node` library (LuaTeX manual ยง10.5) and `node.direct`.
2//!
3//! Node userdata ([`LNode`]) and direct integers both refer to nodes in the
4//! engine's arena. Type ids, subtypes and field names come from the data
5//! recorded from LuaTeX's own `node` library
6//! ([`retex_engine::nodes::LUATEX_NODE_TYPES`]).
7
8use std::collections::HashMap;
9use std::sync::OnceLock;
10
11use mlua::{Lua, MetaMethod, MultiValue, Table, UserData, UserDataMethods, Value};
12use retex_engine::Engine;
13use retex_engine::nodes::{NodeData, NodeRef, id};
14
15use crate::with_engine;
16
17/// Node type information parsed from LuaTeX's API dump.
18struct TypeInfo {
19    names: HashMap<u8, String>,
20    ids: HashMap<String, u8>,
21    fields: HashMap<u8, Vec<(i32, String)>>,
22    subtypes: HashMap<u8, Vec<(i32, String)>>,
23    whatsits: Vec<(u16, String)>,
24    whatsit_fields: HashMap<u16, Vec<(i32, String)>>,
25}
26
27fn info() -> &'static TypeInfo {
28    static T: OnceLock<TypeInfo> = OnceLock::new();
29    T.get_or_init(|| {
30        let data = retex_engine::nodes::LUATEX_NODE_TYPES;
31        let mut ti = TypeInfo {
32            names: HashMap::new(),
33            ids: HashMap::new(),
34            fields: HashMap::new(),
35            subtypes: HashMap::new(),
36            whatsits: Vec::new(),
37            whatsit_fields: HashMap::new(),
38        };
39        let pairs = |s: &str| -> Vec<(i32, String)> {
40            s.split_whitespace()
41                .filter_map(|kv| {
42                    let (k, v) = kv.split_once('=')?;
43                    Some((k.parse().ok()?, v.to_owned()))
44                })
45                .collect()
46        };
47        let mut cur: u8 = 0;
48        for line in data.lines() {
49            let l = line.trim_start();
50            if let Some(rest) = line.strip_prefix("type ") {
51                let mut it = rest.split_whitespace();
52                let i: u8 = it.next().and_then(|x| x.parse().ok()).unwrap_or(0);
53                let name = it.next().unwrap_or("").to_owned();
54                ti.names.insert(i, name.clone());
55                ti.ids.insert(name, i);
56                cur = i;
57            } else if let Some(rest) = l.strip_prefix("fields ") {
58                ti.fields.insert(cur, pairs(rest));
59            } else if let Some(rest) = l.strip_prefix("subtypes ") {
60                ti.subtypes.insert(cur, pairs(rest));
61            } else if let Some(rest) = line.strip_prefix("whatsits ") {
62                ti.whatsits = pairs(rest)
63                    .into_iter()
64                    .map(|(k, v)| (k as u16, v))
65                    .collect();
66            } else if let Some(rest) = l.strip_prefix("whatsit ") {
67                let mut it = rest.splitn(4, ' ');
68                let _name = it.next();
69                let n: u16 = it.next().and_then(|x| x.parse().ok()).unwrap_or(0);
70                let _ = it.next();
71                ti.whatsit_fields.insert(n, pairs(it.next().unwrap_or("")));
72            }
73        }
74        ti
75    })
76}
77
78/// A node userdata.
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub struct LNode(pub u32);
81
82impl UserData for LNode {
83    fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
84        m.add_meta_method(MetaMethod::Index, |lua, this, key: String| {
85            get_field(lua, NodeRef(this.0), &key)
86        });
87        m.add_meta_method(
88            MetaMethod::NewIndex,
89            |lua, this, (key, v): (String, Value)| set_field(lua, NodeRef(this.0), &key, v),
90        );
91        m.add_meta_method(MetaMethod::Eq, |_, this, other: mlua::AnyUserData| {
92            Ok(other
93                .borrow::<LNode>()
94                .map(|o| o.0 == this.0)
95                .unwrap_or(false))
96        });
97        m.add_meta_method(MetaMethod::ToString, |lua, this, ()| {
98            let t = with_engine(lua, |e| e.nodes.id(NodeRef(this.0)))?;
99            let name = info().names.get(&t).cloned().unwrap_or_default();
100            Ok(format!("<node {} < {} > nil : {} 0>", 0, this.0, name))
101        });
102    }
103}
104
105fn to_ref(v: &Value) -> Option<NodeRef> {
106    match v {
107        Value::UserData(u) => u.borrow::<LNode>().ok().map(|n| NodeRef(n.0)),
108        Value::Integer(n) if *n > 0 => Some(NodeRef(*n as u32)),
109        _ => None,
110    }
111}
112
113fn wrap(lua: &Lua, p: NodeRef, direct: bool) -> mlua::Result<Value> {
114    if p.is_null() {
115        return Ok(Value::Nil);
116    }
117    if direct {
118        Ok(Value::Integer(i64::from(p.0)))
119    } else {
120        Ok(Value::UserData(lua.create_userdata(LNode(p.0))?))
121    }
122}
123
124/// Default data for a new node of type `t`.
125fn new_data(t: u8) -> NodeData {
126    use retex_engine::glue::Glue;
127    match t {
128        id::HLIST | id::VLIST => NodeData::Box(Box::default()),
129        id::RULE => NodeData::Rule {
130            width: retex_engine::nodes::RUNNING,
131            depth: retex_engine::nodes::RUNNING,
132            height: retex_engine::nodes::RUNNING,
133            dir: 0,
134            index: 0,
135            left: 0,
136            right: 0,
137        },
138        id::GLUE => NodeData::Glue {
139            spec: Glue::ZERO,
140            leader: NodeRef::NULL,
141        },
142        id::KERN => NodeData::Kern {
143            kern: 0,
144            expansion_factor: 0,
145        },
146        id::PENALTY => NodeData::Penalty { penalty: 0 },
147        id::MARGIN_KERN => NodeData::MarginKern {
148            width: 0,
149            glyph: NodeRef::NULL,
150        },
151        id::GLYPH => NodeData::Glyph(Default::default()),
152        id::DISC => NodeData::Disc {
153            pre: NodeRef::NULL,
154            post: NodeRef::NULL,
155            replace: NodeRef::NULL,
156            penalty: 0,
157        },
158        id::BOUNDARY => NodeData::Boundary { value: 0 },
159        id::MATH => NodeData::Math {
160            surround: 0,
161            glue: Glue::ZERO,
162        },
163        id::ADJUST => NodeData::Adjust {
164            head: NodeRef::NULL,
165        },
166        id::MARK => NodeData::Mark {
167            class: 0,
168            mark: retex_engine::token::empty_list(),
169        },
170        id::DIR => NodeData::Dir { dir: 0, level: 0 },
171        id::LOCAL_PAR => NodeData::LocalPar {
172            pen_inter: 0,
173            pen_broken: 0,
174            dir: 0,
175            box_left: NodeRef::NULL,
176            box_left_width: 0,
177            box_right: NodeRef::NULL,
178            box_right_width: 0,
179        },
180        id::WHATSIT => NodeData::Whatsit(Box::new(retex_engine::nodes::Whatsit::Other {
181            fields: Vec::new(),
182            data: Vec::new(),
183        })),
184        _ => NodeData::Temp,
185    }
186}
187
188fn int(v: &Value) -> i64 {
189    match v {
190        Value::Integer(n) => *n,
191        Value::Number(n) => n.round() as i64,
192        Value::Boolean(b) => i64::from(*b),
193        _ => 0,
194    }
195}
196
197/// Reads field `key` of node `p`.
198fn get_field(lua: &Lua, p: NodeRef, key: &str) -> mlua::Result<Value> {
199    get_field_impl(lua, p, key, false)
200}
201
202fn get_field_impl(lua: &Lua, p: NodeRef, key: &str, direct: bool) -> mlua::Result<Value> {
203    enum R {
204        I(i64),
205        N(NodeRef),
206        F(f64),
207        S(Vec<u8>),
208        Nil,
209    }
210    let r = with_engine(lua, |e| -> R {
211        let n = e.nodes.get(p);
212        match key {
213            "id" => return R::I(i64::from(n.id)),
214            "subtype" => return R::I(i64::from(n.subtype)),
215            "next" => return R::N(n.next),
216            "prev" => return R::N(n.prev),
217            "attr" => return R::Nil,
218            _ => {}
219        }
220        match &n.data {
221            NodeData::Box(b) | NodeData::Unset { b, .. } => match key {
222                "width" => R::I(b.width.into()),
223                "height" => R::I(b.height.into()),
224                "depth" => R::I(b.depth.into()),
225                "shift" => R::I(b.shift.into()),
226                "dir" => R::I(b.dir.into()),
227                "glue_order" => R::I(b.glue_order.into()),
228                "glue_sign" => R::I(b.glue_sign.into()),
229                "glue_set" => R::F(b.glue_set),
230                "head" | "list" => R::N(b.head),
231                _ => R::Nil,
232            },
233            NodeData::Glyph(g) => match key {
234                "char" => R::I(g.chr.into()),
235                "font" => R::I(g.font.into()),
236                "lang" => R::I(g.lang.into()),
237                "left" => R::I(g.left.into()),
238                "right" => R::I(g.right.into()),
239                "uchyph" => R::I(i64::from(g.uchyph)),
240                "components" => R::N(g.components),
241                "xoffset" => R::I(g.xoffset.into()),
242                "yoffset" => R::I(g.yoffset.into()),
243                "expansion_factor" => R::I(g.expansion_factor.into()),
244                "data" => R::I(g.data.into()),
245                "width" => R::I(e.font_char_dimen(g.font, g.chr, 0).into()),
246                "height" => R::I(e.font_char_dimen(g.font, g.chr, 1).into()),
247                "depth" => R::I(e.font_char_dimen(g.font, g.chr, 2).into()),
248                _ => R::Nil,
249            },
250            NodeData::Glue { spec, leader } => match key {
251                "width" => R::I(spec.width.into()),
252                "stretch" => R::I(spec.stretch.into()),
253                "shrink" => R::I(spec.shrink.into()),
254                "stretch_order" => R::I(spec.stretch_order.into()),
255                "shrink_order" => R::I(spec.shrink_order.into()),
256                "leader" => R::N(*leader),
257                _ => R::Nil,
258            },
259            NodeData::Kern {
260                kern,
261                expansion_factor,
262            } => match key {
263                "kern" | "width" => R::I((*kern).into()),
264                "expansion_factor" => R::I((*expansion_factor).into()),
265                _ => R::Nil,
266            },
267            NodeData::Penalty { penalty } => match key {
268                "penalty" => R::I((*penalty).into()),
269                _ => R::Nil,
270            },
271            NodeData::MarginKern { width, glyph } => match key {
272                "width" => R::I((*width).into()),
273                "glyph" => R::N(*glyph),
274                _ => R::Nil,
275            },
276            NodeData::Rule {
277                width,
278                depth,
279                height,
280                dir,
281                index,
282                left,
283                right,
284            } => match key {
285                "width" => R::I((*width).into()),
286                "depth" => R::I((*depth).into()),
287                "height" => R::I((*height).into()),
288                "dir" => R::I((*dir).into()),
289                "index" => R::I((*index).into()),
290                "left" => R::I((*left).into()),
291                "right" => R::I((*right).into()),
292                _ => R::Nil,
293            },
294            NodeData::Disc {
295                pre,
296                post,
297                replace,
298                penalty,
299            } => match key {
300                "pre" => R::N(*pre),
301                "post" => R::N(*post),
302                "replace" => R::N(*replace),
303                "penalty" => R::I((*penalty).into()),
304                _ => R::Nil,
305            },
306            NodeData::Boundary { value } => match key {
307                "value" => R::I((*value).into()),
308                _ => R::Nil,
309            },
310            NodeData::Mark { class, mark } => match key {
311                "class" => R::I((*class).into()),
312                "mark" => {
313                    let mark = mark.clone();
314                    R::S(e.print_to_string(|e| e.show_token_list(&mark, 0, mark.len(), i64::MAX)))
315                }
316                _ => R::Nil,
317            },
318            NodeData::Ins {
319                cost,
320                depth,
321                height,
322                head,
323                ..
324            } => match key {
325                "cost" => R::I((*cost).into()),
326                "depth" => R::I((*depth).into()),
327                "height" => R::I((*height).into()),
328                "head" | "list" => R::N(*head),
329                _ => R::Nil,
330            },
331            NodeData::Adjust { head } => match key {
332                "head" | "list" => R::N(*head),
333                _ => R::Nil,
334            },
335            NodeData::Dir { dir, level } => match key {
336                "dir" => R::I((*dir).into()),
337                "level" => R::I((*level).into()),
338                _ => R::Nil,
339            },
340            NodeData::Math { surround, glue } => match key {
341                "surround" => R::I((*surround).into()),
342                "width" => R::I(glue.width.into()),
343                "stretch" => R::I(glue.stretch.into()),
344                "shrink" => R::I(glue.shrink.into()),
345                "stretch_order" => R::I(glue.stretch_order.into()),
346                "shrink_order" => R::I(glue.shrink_order.into()),
347                _ => R::Nil,
348            },
349            _ => R::Nil,
350        }
351    })?;
352    Ok(match r {
353        R::I(i) => Value::Integer(i),
354        R::N(n) => wrap(lua, n, direct)?,
355        R::F(f) => Value::Number(f),
356        R::S(s) => Value::String(lua.create_string(&s)?),
357        R::Nil => Value::Nil,
358    })
359}
360
361/// Writes field `key` of node `p`.
362fn set_field(lua: &Lua, p: NodeRef, key: &str, v: Value) -> mlua::Result<()> {
363    let r = to_ref(&v).unwrap_or(NodeRef::NULL);
364    let i = int(&v);
365    let f = match &v {
366        Value::Number(x) => *x,
367        Value::Integer(x) => *x as f64,
368        _ => 0.0,
369    };
370    with_engine(lua, |e| {
371        let n = e.nodes.get_mut(p);
372        match key {
373            "next" => {
374                n.next = r;
375                return;
376            }
377            "prev" => {
378                n.prev = r;
379                return;
380            }
381            "subtype" => {
382                n.subtype = i as u16;
383                return;
384            }
385            _ => {}
386        }
387        match &mut n.data {
388            NodeData::Box(b) | NodeData::Unset { b, .. } => match key {
389                "width" => b.width = i as i32,
390                "height" => b.height = i as i32,
391                "depth" => b.depth = i as i32,
392                "shift" => b.shift = i as i32,
393                "dir" => b.dir = i as i8,
394                "glue_order" => b.glue_order = i as u8,
395                "glue_sign" => b.glue_sign = i as u8,
396                "glue_set" => b.glue_set = f64::from(f as f32),
397                "head" | "list" => b.head = r,
398                _ => {}
399            },
400            NodeData::Glyph(g) => match key {
401                "char" => g.chr = i as u32,
402                "font" => g.font = i as u32,
403                "lang" => g.lang = i as u16,
404                "left" => g.left = i as u8,
405                "right" => g.right = i as u8,
406                "uchyph" => g.uchyph = i != 0,
407                "components" => g.components = r,
408                "xoffset" => g.xoffset = i as i32,
409                "yoffset" => g.yoffset = i as i32,
410                "expansion_factor" => g.expansion_factor = i as i32,
411                "data" => g.data = i as i32,
412                _ => {}
413            },
414            NodeData::Glue { spec, leader } => match key {
415                "width" => spec.width = i as i32,
416                "stretch" => spec.stretch = i as i32,
417                "shrink" => spec.shrink = i as i32,
418                "stretch_order" => spec.stretch_order = i as u8,
419                "shrink_order" => spec.shrink_order = i as u8,
420                "leader" => *leader = r,
421                _ => {}
422            },
423            NodeData::Kern {
424                kern,
425                expansion_factor,
426            } => {
427                if key == "kern" || key == "width" {
428                    *kern = i as i32;
429                } else if key == "expansion_factor" {
430                    *expansion_factor = i as i32;
431                }
432            }
433            NodeData::MarginKern { width, glyph } => match key {
434                "width" => *width = i as i32,
435                "glyph" => *glyph = r,
436                _ => {}
437            },
438            NodeData::Penalty { penalty } => {
439                if key == "penalty" {
440                    *penalty = i as i32;
441                }
442            }
443            NodeData::Rule {
444                width,
445                depth,
446                height,
447                ..
448            } => match key {
449                "width" => *width = i as i32,
450                "depth" => *depth = i as i32,
451                "height" => *height = i as i32,
452                _ => {}
453            },
454            NodeData::Disc {
455                pre,
456                post,
457                replace,
458                penalty,
459            } => match key {
460                "pre" => *pre = r,
461                "post" => *post = r,
462                "replace" => *replace = r,
463                "penalty" => *penalty = i as i32,
464                _ => {}
465            },
466            NodeData::Boundary { value } => {
467                if key == "value" {
468                    *value = i as i32;
469                }
470            }
471            NodeData::Mark { class, .. } => {
472                if key == "class" {
473                    *class = i as u32;
474                }
475            }
476            _ => {}
477        }
478    })
479}
480
481/// Installs `node` and `node.direct`.
482pub fn install(lua: &Lua) -> mlua::Result<()> {
483    let node = lua.create_table()?;
484    let direct = lua.create_table()?;
485    for (t, is_direct) in [(&node, false), (&direct, true)] {
486        install_common(lua, t, is_direct)?;
487    }
488    direct.set(
489        "todirect",
490        lua.create_function(|_, v: Value| Ok(to_ref(&v).map(|p| i64::from(p.0))))?,
491    )?;
492    direct.set(
493        "tonode",
494        lua.create_function(|lua, v: Value| match to_ref(&v) {
495            Some(p) => wrap(lua, p, false),
496            None => Ok(Value::Nil),
497        })?,
498    )?;
499    node.set("direct", direct)?;
500    lua.globals().set("node", node)?;
501    Ok(())
502}
503
504fn install_common(lua: &Lua, t: &Table, direct: bool) -> mlua::Result<()> {
505    t.set(
506        "types",
507        lua.create_function(|lua, ()| {
508            let out = lua.create_table()?;
509            for (k, v) in &info().names {
510                out.set(*k, v.as_str())?;
511            }
512            Ok(out)
513        })?,
514    )?;
515    t.set(
516        "whatsits",
517        lua.create_function(|lua, ()| {
518            let out = lua.create_table()?;
519            for (k, v) in &info().whatsits {
520                out.set(*k, v.as_str())?;
521            }
522            Ok(out)
523        })?,
524    )?;
525    t.set(
526        "id",
527        lua.create_function(|_, name: String| Ok(info().ids.get(&name).copied()))?,
528    )?;
529    t.set(
530        "subtype",
531        lua.create_function(|_, name: String| {
532            Ok(info()
533                .whatsits
534                .iter()
535                .find(|(_, n)| *n == name)
536                .map(|(k, _)| *k))
537        })?,
538    )?;
539    t.set(
540        "type",
541        lua.create_function(|lua, v: Value| match &v {
542            Value::Integer(n) if !matches!(v, Value::UserData(_)) => {
543                Ok(info().names.get(&(*n as u8)).cloned())
544            }
545            _ => match to_ref(&v) {
546                Some(p) => {
547                    let t = with_engine(lua, |e| e.nodes.id(p))?;
548                    Ok(info().names.get(&t).cloned())
549                }
550                None => Ok(None),
551            },
552        })?,
553    )?;
554    t.set(
555        "subtypes",
556        lua.create_function(|lua, v: Value| {
557            let idn = match &v {
558                Value::Integer(n) => *n as u8,
559                Value::String(s) => match info().ids.get(&*s.to_str()?) {
560                    Some(i) => *i,
561                    None => return Ok(Value::Nil),
562                },
563                _ => return Ok(Value::Nil),
564            };
565            let out = lua.create_table()?;
566            if let Some(st) = info().subtypes.get(&idn) {
567                for (k, n) in st {
568                    out.set(*k, n.as_str())?;
569                }
570            }
571            Ok(Value::Table(out))
572        })?,
573    )?;
574    t.set(
575        "fields",
576        lua.create_function(|lua, (v, sub): (Value, Option<i64>)| {
577            let idn = match &v {
578                Value::Integer(n) => *n as u8,
579                Value::String(s) => match info().ids.get(&*s.to_str()?) {
580                    Some(i) => *i,
581                    None => return Ok(Value::Nil),
582                },
583                _ => return Ok(Value::Nil),
584            };
585            let list = if idn == id::WHATSIT {
586                sub.and_then(|s| info().whatsit_fields.get(&(s as u16)))
587            } else {
588                info().fields.get(&idn)
589            };
590            let out = lua.create_table()?;
591            if let Some(fl) = list {
592                for (k, n) in fl {
593                    out.set(*k, n.as_str())?;
594                }
595            }
596            Ok(Value::Table(out))
597        })?,
598    )?;
599    t.set(
600        "is_node",
601        lua.create_function(move |_, v: Value| {
602            Ok(match &v {
603                Value::UserData(u) => u.borrow::<LNode>().is_ok(),
604                Value::Integer(n) => direct && *n > 0,
605                _ => false,
606            })
607        })?,
608    )?;
609    t.set(
610        "new",
611        lua.create_function(move |lua, (ty, sub): (Value, Option<Value>)| {
612            let t = match &ty {
613                Value::Integer(n) => *n as u8,
614                Value::String(s) => *info()
615                    .ids
616                    .get(&*s.to_str()?)
617                    .ok_or_else(|| mlua::Error::runtime("unknown node type"))?,
618                _ => return Err(mlua::Error::runtime("node type expected")),
619            };
620            let st = match &sub {
621                Some(Value::Integer(n)) => *n as u16,
622                Some(Value::String(s)) => {
623                    let s = s.to_str()?.to_owned();
624                    info()
625                        .whatsits
626                        .iter()
627                        .find(|(_, n)| *n == s)
628                        .map(|(k, _)| *k)
629                        .unwrap_or(0)
630                }
631                _ => 0,
632            };
633            let p = with_engine(lua, |e| e.nodes.alloc(t, st, new_data(t)))?;
634            wrap(lua, p, direct)
635        })?,
636    )?;
637    t.set(
638        "free",
639        lua.create_function(|lua, v: Value| {
640            if let Some(p) = to_ref(&v) {
641                with_engine(lua, |e| e.nodes.free(p))?;
642            }
643            Ok(())
644        })?,
645    )?;
646    t.set(
647        "flush_node",
648        lua.create_function(|lua, v: Value| {
649            if let Some(p) = to_ref(&v) {
650                with_engine(lua, |e| e.nodes.free(p))?;
651            }
652            Ok(())
653        })?,
654    )?;
655    t.set(
656        "flush_list",
657        lua.create_function(|lua, v: Value| {
658            if let Some(p) = to_ref(&v) {
659                with_engine(lua, |e| e.nodes.flush_list(p))?;
660            }
661            Ok(())
662        })?,
663    )?;
664    t.set(
665        "copy",
666        lua.create_function(move |lua, v: Value| match to_ref(&v) {
667            Some(p) => {
668                let q = with_engine(lua, |e| e.nodes.copy_node(p))?;
669                wrap(lua, q, direct)
670            }
671            None => Ok(Value::Nil),
672        })?,
673    )?;
674    t.set(
675        "copy_list",
676        lua.create_function(move |lua, v: Value| match to_ref(&v) {
677            Some(p) => {
678                let q = with_engine(lua, |e| e.nodes.copy_list(p))?;
679                wrap(lua, q, direct)
680            }
681            None => Ok(Value::Nil),
682        })?,
683    )?;
684    t.set(
685        "tail",
686        lua.create_function(move |lua, v: Value| match to_ref(&v) {
687            Some(p) => {
688                let q = with_engine(lua, |e| e.nodes.tail_of(p))?;
689                wrap(lua, q, direct)
690            }
691            None => Ok(Value::Nil),
692        })?,
693    )?;
694    t.set(
695        "slide",
696        lua.create_function(move |lua, v: Value| match to_ref(&v) {
697            Some(p) => {
698                let q = with_engine(lua, |e| slide(e, p))?;
699                wrap(lua, q, direct)
700            }
701            None => Ok(Value::Nil),
702        })?,
703    )?;
704    t.set(
705        "length",
706        lua.create_function(|lua, v: Value| match to_ref(&v) {
707            Some(p) => with_engine(lua, |e| count(e, p, None)),
708            None => Ok(0),
709        })?,
710    )?;
711    t.set(
712        "count",
713        lua.create_function(|lua, (ty, v): (i64, Value)| match to_ref(&v) {
714            Some(p) => with_engine(lua, |e| count(e, p, Some(ty as u8))),
715            None => Ok(0),
716        })?,
717    )?;
718    for (name, by_id) in [("traverse", false), ("traverse_id", true)] {
719        t.set(
720            name,
721            lua.create_function(move |lua, args: MultiValue| {
722                let v: Vec<Value> = args.into_iter().collect();
723                let (filter, start) = if by_id {
724                    (
725                        Some(int(v.first().unwrap_or(&Value::Nil)) as u8),
726                        v.get(1).cloned(),
727                    )
728                } else {
729                    (None, v.first().cloned())
730                };
731                let start = start.and_then(|s| to_ref(&s)).unwrap_or(NodeRef::NULL);
732                let iter = lua.create_function(move |lua, (_s, cur): (Value, Value)| {
733                    let next = match to_ref(&cur) {
734                        Some(c) => with_engine(lua, |e| e.nodes.next(c))?,
735                        None => start,
736                    };
737                    let mut p = next;
738                    let found = with_engine(lua, |e| {
739                        while !p.is_null() {
740                            if filter.is_none_or(|f| e.nodes.id(p) == f) {
741                                return (p, e.nodes.id(p), e.nodes.get(p).subtype);
742                            }
743                            p = e.nodes.next(p);
744                        }
745                        (NodeRef::NULL, 0, 0)
746                    })?;
747                    if found.0.is_null() {
748                        return Ok(MultiValue::new());
749                    }
750                    Ok(MultiValue::from_iter([
751                        wrap(lua, found.0, direct)?,
752                        Value::Integer(i64::from(found.1)),
753                        Value::Integer(i64::from(found.2)),
754                    ]))
755                })?;
756                Ok((iter, Value::Nil, Value::Nil))
757            })?,
758        )?;
759    }
760    t.set(
761        "insert_after",
762        lua.create_function(move |lua, (h, c, n): (Value, Value, Value)| {
763            let (Some(n), head) = (to_ref(&n), to_ref(&h)) else {
764                return Ok(MultiValue::new());
765            };
766            let cur = to_ref(&c);
767            let head = with_engine(lua, |e| match (head, cur) {
768                (None, _) | (_, None) => n,
769                (Some(h), Some(c)) => {
770                    let nx = e.nodes.next(c);
771                    e.nodes.link(c, n);
772                    let tail = e.nodes.tail_of(n);
773                    e.nodes.link(tail, nx);
774                    h
775                }
776            })?;
777            Ok(MultiValue::from_iter([
778                wrap(lua, head, direct)?,
779                wrap(lua, n, direct)?,
780            ]))
781        })?,
782    )?;
783    t.set(
784        "insert_before",
785        lua.create_function(move |lua, (h, c, n): (Value, Value, Value)| {
786            let Some(n) = to_ref(&n) else {
787                return Ok(MultiValue::new());
788            };
789            let head = to_ref(&h);
790            let cur = to_ref(&c);
791            let head = with_engine(lua, |e| match (head, cur) {
792                (None, _) => n,
793                (Some(h), None) => {
794                    let t = e.nodes.tail_of(h);
795                    e.nodes.link(t, n);
796                    h
797                }
798                (Some(h), Some(c)) => {
799                    let pv = e.nodes.get(c).prev;
800                    let tail = e.nodes.tail_of(n);
801                    e.nodes.link(tail, c);
802                    if c == h || pv.is_null() {
803                        e.nodes.get_mut(n).prev = NodeRef::NULL;
804                        n
805                    } else {
806                        e.nodes.link(pv, n);
807                        h
808                    }
809                }
810            })?;
811            Ok(MultiValue::from_iter([
812                wrap(lua, head, direct)?,
813                wrap(lua, n, direct)?,
814            ]))
815        })?,
816    )?;
817    t.set(
818        "remove",
819        lua.create_function(move |lua, (h, c): (Value, Value)| {
820            let (Some(h), Some(c)) = (to_ref(&h), to_ref(&c)) else {
821                return Ok(MultiValue::new());
822            };
823            let (head, next) = with_engine(lua, |e| {
824                let pv = e.nodes.get(c).prev;
825                let nx = e.nodes.next(c);
826                let head = if c == h {
827                    nx
828                } else {
829                    e.nodes.link(pv, nx);
830                    h
831                };
832                if !nx.is_null() && c == h {
833                    e.nodes.get_mut(nx).prev = NodeRef::NULL;
834                }
835                let n = e.nodes.get_mut(c);
836                n.next = NodeRef::NULL;
837                n.prev = NodeRef::NULL;
838                (head, nx)
839            })?;
840            Ok(MultiValue::from_iter([
841                wrap(lua, head, direct)?,
842                wrap(lua, next, direct)?,
843            ]))
844        })?,
845    )?;
846    t.set(
847        "has_field",
848        lua.create_function(|lua, (v, f): (Value, String)| match to_ref(&v) {
849            Some(p) => {
850                let t = with_engine(lua, |e| e.nodes.id(p))?;
851                Ok(info()
852                    .fields
853                    .get(&t)
854                    .is_some_and(|fl| fl.iter().any(|(_, n)| *n == f)))
855            }
856            None => Ok(false),
857        })?,
858    )?;
859    t.set(
860        "getfield",
861        lua.create_function(move |lua, (v, f): (Value, String)| match to_ref(&v) {
862            Some(p) => get_field_impl(lua, p, &f, direct),
863            None => Ok(Value::Nil),
864        })?,
865    )?;
866    t.set(
867        "setfield",
868        lua.create_function(|lua, (v, f, x): (Value, String, Value)| match to_ref(&v) {
869            Some(p) => set_field(lua, p, &f, x),
870            None => Ok(()),
871        })?,
872    )?;
873    // Direct-style accessors (also accepted by node.* in LuaTeX).
874    for name in [
875        "id", "subtype", "next", "prev", "char", "font", "width", "height", "depth", "list",
876        "penalty", "kern", "leader",
877    ] {
878        let key = if name == "list" { "head" } else { name };
879        if name != "id" && name != "subtype" {
880            t.set(
881                format!("get{name}"),
882                lua.create_function(move |lua, v: Value| match to_ref(&v) {
883                    Some(p) => get_field_impl(lua, p, key, direct),
884                    None => Ok(Value::Nil),
885                })?,
886            )?;
887        } else {
888            t.set(
889                format!("get{name}"),
890                lua.create_function(move |lua, v: Value| match to_ref(&v) {
891                    Some(p) => get_field_impl(lua, p, key, direct),
892                    None => Ok(Value::Nil),
893                })?,
894            )?;
895        }
896        t.set(
897            format!("set{name}"),
898            lua.create_function(move |lua, (v, x): (Value, Value)| match to_ref(&v) {
899                Some(p) => set_field(lua, p, key, x),
900                None => Ok(()),
901            })?,
902        )?;
903    }
904    // Engine operations that ltluatex installs as callback defaults; the
905    // real implementations come with the typesetting code.
906    for name in [
907        "mlist_to_hlist",
908        "hpack",
909        "vpack",
910        "rangedimensions",
911        "kerning",
912        "ligaturing",
913        "hyphenating",
914        "protect_glyphs",
915        "unprotect_glyphs",
916        "write",
917        "last_node",
918        "end_of_math",
919        "flatten_discretionaries",
920    ] {
921        t.set(
922            name,
923            lua.create_function(move |_, args: MultiValue| Ok(args))?,
924        )?;
925    }
926    // The font of a math family in text size.
927    t.set(
928        "family_font",
929        lua.create_function(|lua, fam: i64| {
930            if !(0..retex_engine::eqtb::NUM_FAMILIES as i64).contains(&fam) {
931                return Ok(0);
932            }
933            with_engine(lua, |e| i64::from(e.fam_fnt(fam as u32, 0)))
934        })?,
935    )?;
936    t.set(
937        "dimensions",
938        lua.create_function(|lua, args: MultiValue| {
939            // dimensions([glue_set, glue_sign, glue_order,] n [, t]): the
940            // natural size of the list from n up to t (exclusive).
941            let refs: Vec<NodeRef> = args.iter().filter_map(to_ref).collect();
942            let Some(&first) = refs.first() else {
943                return Ok((0, 0, 0));
944            };
945            let stop = refs.get(1).copied().unwrap_or(NodeRef::NULL);
946            with_engine(lua, |e| e.natural_dimensions(first, stop))
947        })?,
948    )?;
949    t.set(
950        "has_attribute",
951        lua.create_function(|lua, (v, n, val): (Value, i64, Option<i64>)| {
952            let Some(p) = to_ref(&v) else {
953                return Ok(None);
954            };
955            let a = with_engine(lua, |e| {
956                e.nodes
957                    .get(p)
958                    .attr
959                    .as_ref()
960                    .and_then(|l| l.iter().find(|(k, _)| i64::from(*k) == n).map(|x| x.1))
961            })?;
962            Ok(match (a, val) {
963                (Some(x), Some(w)) if i64::from(x) != w => None,
964                (a, _) => a.map(i64::from),
965            })
966        })?,
967    )?;
968    t.set(
969        "set_attribute",
970        lua.create_function(|lua, (v, n, val): (Value, i64, i64)| {
971            if let Some(p) = to_ref(&v) {
972                with_engine(lua, |e| set_attr(e, p, n as u32, Some(val as i32)))?;
973            }
974            Ok(())
975        })?,
976    )?;
977    t.set(
978        "unset_attribute",
979        lua.create_function(|lua, (v, n): (Value, i64)| {
980            if let Some(p) = to_ref(&v) {
981                with_engine(lua, |e| set_attr(e, p, n as u32, None))?;
982            }
983            Ok(())
984        })?,
985    )?;
986    Ok(())
987}
988
989fn set_attr(e: &mut Engine, p: NodeRef, n: u32, v: Option<i32>) {
990    let node = e.nodes.get_mut(p);
991    let mut list: Vec<(u32, i32)> = node.attr.as_ref().map(|l| l.to_vec()).unwrap_or_default();
992    list.retain(|(k, _)| *k != n);
993    if let Some(v) = v {
994        list.push((n, v));
995        list.sort();
996    }
997    node.attr = if list.is_empty() {
998        None
999    } else {
1000        Some(std::rc::Rc::from(list))
1001    };
1002}
1003
1004fn slide(e: &mut Engine, mut p: NodeRef) -> NodeRef {
1005    loop {
1006        let n = e.nodes.next(p);
1007        if n.is_null() {
1008            return p;
1009        }
1010        e.nodes.get_mut(n).prev = p;
1011        p = n;
1012    }
1013}
1014
1015fn count(e: &Engine, mut p: NodeRef, ty: Option<u8>) -> i64 {
1016    let mut n = 0;
1017    while !p.is_null() {
1018        if ty.is_none_or(|t| e.nodes.id(p) == t) {
1019            n += 1;
1020        }
1021        p = e.nodes.next(p);
1022    }
1023    n
1024}