Skip to main content

retex_engine/
primitives.rs

1//! Lookup tables over LuaTeX's primitives ([`crate::primitives_gen`]).
2
3use std::collections::HashMap;
4use std::sync::OnceLock;
5
6use crate::cmd::Cmd;
7use crate::primitives_gen::{Group, PRIMITIVES, Primitive};
8
9struct Tables {
10    by_name: HashMap<&'static [u8], &'static Primitive>,
11    by_code: HashMap<(Cmd, u32), &'static str>,
12}
13
14fn tables() -> &'static Tables {
15    static T: OnceLock<Tables> = OnceLock::new();
16    T.get_or_init(|| {
17        let mut by_name = HashMap::with_capacity(PRIMITIVES.len());
18        let mut by_code = HashMap::with_capacity(PRIMITIVES.len());
19        for p in PRIMITIVES {
20            by_name.insert(p.name.as_bytes(), p);
21            if p.cmd == Cmd::UndefinedCs {
22                continue;
23            }
24            // When several names share a code, LuaTeX prints the one defined
25            // last; `\-` and `\explicitdiscretionary` is the only such pair and
26            // the longer name wins.
27            by_code
28                .entry((p.cmd, p.chr))
29                .and_modify(|n: &mut &'static str| {
30                    if p.name.len() > n.len() {
31                        *n = p.name;
32                    }
33                })
34                .or_insert(p.name);
35        }
36        Tables { by_name, by_code }
37    })
38}
39
40/// The primitive with the given name, if any.
41pub fn lookup(name: &[u8]) -> Option<&'static Primitive> {
42    tables().by_name.get(name).copied()
43}
44
45/// The printed name of the primitive with this command and modifier.
46pub fn name_of(cmd: Cmd, chr: u32) -> Option<&'static str> {
47    tables().by_code.get(&(cmd, chr)).copied()
48}
49
50/// The primitives of the given `tex.extraprimitives` group.
51pub fn group(g: Group) -> impl Iterator<Item = &'static Primitive> {
52    PRIMITIVES.iter().filter(move |p| p.group == g)
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn lookups() {
61        let p = lookup(b"relax").unwrap();
62        assert_eq!(p.cmd, Cmd::Relax);
63        assert_eq!(
64            name_of(Cmd::Discretionary, 1),
65            Some("explicitdiscretionary")
66        );
67        assert_eq!(name_of(Cmd::IfTest, 12), Some("ifx"));
68        assert_eq!(group(Group::Core).count(), 1);
69        assert_eq!(group(Group::Tex).count(), 340);
70    }
71}