texlang_stdlib/
catcode.rs

1//! The `\catcode` primitive
2
3use std::collections::HashMap;
4use texlang::token;
5use texlang::token::CatCode;
6use texlang::traits::*;
7use texlang::*;
8
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct Component {
11    #[cfg_attr(
12        feature = "serde",
13        serde(
14            serialize_with = "texcraft_stdext::serde_tools::serialize_array",
15            deserialize_with = "texcraft_stdext::serde_tools::deserialize_array",
16        )
17    )]
18    low: [CatCode; 128],
19    high: HashMap<usize, CatCode>,
20    default: CatCode,
21}
22
23impl Component {
24    #[inline]
25    pub fn get(&self, u: usize) -> &CatCode {
26        match self.low.get(u) {
27            None => self.high.get(&u).unwrap_or(&self.default),
28            Some(cat_code) => cat_code,
29        }
30    }
31
32    #[inline]
33    pub fn get_mut(&mut self, u: usize) -> &mut CatCode {
34        match self.low.get_mut(u) {
35            None => self.high.entry(u).or_insert(self.default),
36            Some(cat_code) => cat_code,
37        }
38    }
39}
40
41impl Default for Component {
42    fn default() -> Self {
43        Self {
44            low: CatCode::PLAIN_TEX_DEFAULTS,
45            high: Default::default(),
46            default: Default::default(),
47        }
48    }
49}
50
51#[inline]
52pub fn cat_code<S: HasComponent<Component>>(state: &S, c: char) -> CatCode {
53    *state.component().get(c as usize)
54}
55
56pub const CATCODE_DOC: &str = "Get or set a catcode register";
57
58/// Get the `\catcode` command.
59pub fn get_catcode<S: HasComponent<Component>>() -> command::BuiltIn<S> {
60    variable::Command::new_array(
61        |state: &S, index: variable::Index| -> &CatCode { state.component().get(index.0) },
62        |state: &mut S, index: variable::Index| -> &mut CatCode {
63            state.component_mut().get_mut(index.0)
64        },
65        variable::IndexResolver::Dynamic(
66            |_: token::Token,
67             input: &mut vm::ExpandedStream<S>|
68             -> command::Result<variable::Index> { Ok(usize::parse(input)?.into()) },
69        ),
70    )
71    .into()
72}
73
74#[cfg(test)]
75mod tests {
76    use std::collections::HashMap;
77
78    use super::*;
79    use crate::script;
80    use crate::testing::*;
81    use crate::the;
82
83    #[derive(Default)]
84    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
85    struct State {
86        catcode: Component,
87        script: script::Component,
88    }
89
90    impl TexlangState for State {}
91
92    implement_has_component![State, (Component, catcode), (script::Component, script),];
93
94    fn initial_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
95        HashMap::from([("the", the::get_the()), ("catcode", get_catcode())])
96    }
97
98    test_suite![
99        expansion_equality_tests(
100            (catcode_base_case, r"\catcode 48 11 \the\catcode 48", r"11"),
101            (
102                grouping,
103                r"{\catcode 48 11 \the\catcode 48}\the\catcode 48",
104                r"1112"
105            ),
106            (catcode_default, r"\the\catcode 48", r"12"),
107        ),
108        serde_tests(
109            (serde_low, r"\catcode 48 11 ", r"\the\catcode 48"),
110            (serde_high, r"\catcode 480 11 ", r"\the\catcode 480"),
111        ),
112        failure_tests(
113            (catcode_value_too_large, r"\catcode 48 16"),
114            (catcode_value_is_negative_large, r"\catcode 48 -1"),
115        ),
116    ];
117}