Skip to main content

mathtex_engine/
primitive.rs

1use alloc::collections::BTreeMap;
2use alloc::string::{String, ToString};
3
4use crate::profile::{PrimitiveKind, PrimitiveOpcode, PrimitiveSpec};
5
6/// Maps primitive control sequence names to their dispatch opcodes and semantic kinds.
7#[derive(Clone, Debug, Default, PartialEq, Eq)]
8pub struct PrimitiveRegistry {
9    entries: BTreeMap<String, PrimitiveEntry>,
10}
11
12impl PrimitiveRegistry {
13    /// Builds a registry from a slice of specs, returning an error on the first duplicate name.
14    pub fn from_specs(specs: &[PrimitiveSpec]) -> Result<Self, PrimitiveRegistryError> {
15        let mut registry = Self::default();
16        for spec in specs {
17            registry.insert(spec)?;
18        }
19        Ok(registry)
20    }
21
22    /// Inserts a single primitive spec, returning an error if the name is already registered.
23    pub fn insert(&mut self, spec: &PrimitiveSpec) -> Result<(), PrimitiveRegistryError> {
24        let entry = PrimitiveEntry {
25            name: spec.name.to_string(),
26            opcode: spec.opcode,
27            kind: spec.kind,
28        };
29
30        if self.entries.contains_key(&entry.name) {
31            return Err(PrimitiveRegistryError::DuplicateName { name: entry.name });
32        }
33
34        self.entries.insert(entry.name.clone(), entry);
35        Ok(())
36    }
37
38    /// Look up a primitive by control sequence name without leading backslash.
39    #[must_use]
40    pub fn get(&self, name: &str) -> Option<&PrimitiveEntry> {
41        self.entries.get(name)
42    }
43
44    /// Returns the number of registered primitives.
45    #[must_use]
46    pub fn len(&self) -> usize {
47        self.entries.len()
48    }
49
50    /// Returns true when no primitives have been registered.
51    #[must_use]
52    pub fn is_empty(&self) -> bool {
53        self.entries.is_empty()
54    }
55}
56
57/// Resolved information for a single registered primitive.
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct PrimitiveEntry {
60    /// Primitive control sequence name without leading backslash.
61    pub name: String,
62    /// Numeric dispatch key for translated/native engine code.
63    pub opcode: PrimitiveOpcode,
64    /// Semantic category of the primitive.
65    pub kind: PrimitiveKind,
66}
67
68/// Error returned when building a `PrimitiveRegistry`.
69#[derive(Clone, Debug, PartialEq, Eq)]
70#[non_exhaustive]
71pub enum PrimitiveRegistryError {
72    /// Two primitives were registered under the same name.
73    DuplicateName {
74        /// The duplicated control sequence name.
75        name: String,
76    },
77}
78
79#[cfg(test)]
80mod tests {
81    use alloc::borrow::Cow;
82
83    use super::*;
84
85    #[test]
86    fn registry_rejects_duplicate_names() {
87        let error = PrimitiveRegistry::from_specs(&[
88            PrimitiveSpec {
89                name: Cow::Borrowed("input"),
90                opcode: PrimitiveOpcode(1),
91                kind: PrimitiveKind::Resource,
92            },
93            PrimitiveSpec {
94                name: Cow::Borrowed("input"),
95                opcode: PrimitiveOpcode(2),
96                kind: PrimitiveKind::Resource,
97            },
98        ])
99        .expect_err("duplicate names should fail");
100
101        assert_eq!(
102            error,
103            PrimitiveRegistryError::DuplicateName {
104                name: "input".to_string(),
105            }
106        );
107    }
108}