texlang_stdlib/
codes.rs

1//! Primitives for setting codes (`\catcode`, `\mathcode`, etc.)
2
3use std::collections::HashMap;
4use std::fmt::Debug;
5use texlang::prelude as txl;
6use texlang::traits::*;
7use texlang::types::CatCode;
8use texlang::*;
9
10pub trait Code: Copy + Debug + Default + variable::SupportedType {
11    const COMMAND_DOC: &'static str;
12    fn default_low_values() -> [Self; 128] {
13        [Self::default(); 128]
14    }
15}
16
17impl Code for CatCode {
18    const COMMAND_DOC: &'static str = "Get or set a cat code register";
19    fn default_low_values() -> [Self; 128] {
20        CatCode::PLAIN_TEX_DEFAULTS
21    }
22}
23
24impl Code for types::MathCode {
25    const COMMAND_DOC: &'static str = "Get or set a math code register";
26    // TODO: default_low_values
27}
28
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct Component<T: Code> {
31    #[cfg_attr(feature = "serde", serde(with = "texcraft_stdext::serde_tools::array"))]
32    low: [T; 128],
33    high: HashMap<usize, T>,
34    default: T,
35}
36
37impl<T: Code> Component<T> {
38    #[inline]
39    pub fn get(&self, u: usize) -> &T {
40        match self.low.get(u) {
41            None => self.high.get(&u).unwrap_or(&self.default),
42            Some(cat_code) => cat_code,
43        }
44    }
45    #[inline]
46    pub fn get_mut(&mut self, u: usize) -> &mut T {
47        match self.low.get_mut(u) {
48            None => self.high.entry(u).or_insert(self.default),
49            Some(cat_code) => cat_code,
50        }
51    }
52}
53
54impl<T: Code> Default for Component<T> {
55    fn default() -> Self {
56        Self {
57            low: T::default_low_values(),
58            high: Default::default(),
59            default: Default::default(),
60        }
61    }
62}
63
64/// Return the currently defined cat code of a character.
65#[inline]
66pub fn cat_code<S: HasComponent<Component<CatCode>>>(state: &S, c: char) -> CatCode {
67    get_value(state, c)
68}
69
70/// Get the `\catcode` command.
71pub fn get_catcode<S: HasComponent<Component<CatCode>>>() -> command::BuiltIn<S> {
72    get_command::<CatCode, S>()
73}
74
75/// Return the currently defined math code of a character.
76#[inline]
77pub fn math_code<S: HasComponent<Component<types::MathCode>>>(
78    state: &S,
79    c: char,
80) -> types::MathCode {
81    get_value(state, c)
82}
83
84/// Get the `\mathcode` command.
85pub fn get_mathcode<S: HasComponent<Component<types::MathCode>>>() -> command::BuiltIn<S> {
86    get_command::<types::MathCode, S>()
87}
88
89/// Return the currently defined math code of a character.
90#[inline]
91fn get_value<T: Code, S: HasComponent<Component<T>>>(state: &S, c: char) -> T {
92    *state.component().get(c as usize)
93}
94
95fn get_command<T: Code, S: HasComponent<Component<T>>>() -> command::BuiltIn<S> {
96    let cmd: command::BuiltIn<S> = variable::Command::new_array(
97        |state: &S, index: variable::Index| -> &T { state.component().get(index.0) },
98        |state: &mut S, index: variable::Index| -> &mut T {
99            state.component_mut().get_mut(index.0)
100        },
101        variable::IndexResolver::Dynamic(
102            |_: token::Token, input: &mut vm::ExpandedStream<S>| -> txl::Result<variable::Index> {
103                let c = char::parse(input)?;
104                let u = c as usize;
105                Ok(u.into())
106            },
107        ),
108    )
109    .into();
110    cmd.with_doc(T::COMMAND_DOC)
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use crate::the;
117    use texlang_testing::*;
118
119    #[derive(Default)]
120    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121    struct State {
122        catcode: Component<CatCode>,
123        math_code: Component<types::MathCode>,
124        testing: TestingComponent,
125    }
126
127    impl TexlangState for State {
128        fn recoverable_error_hook(
129            &self,
130            recoverable_error: error::TracedTexError,
131        ) -> Result<(), Box<dyn error::TexError>> {
132            TestingComponent::recoverable_error_hook(self, recoverable_error)
133        }
134    }
135    impl the::TheCompatible for State {}
136
137    implement_has_component![State {
138        catcode: Component<CatCode>,
139        math_code: Component<types::MathCode>,
140        testing: TestingComponent,
141    }];
142
143    fn built_in_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
144        HashMap::from([
145            ("the", the::get_the()),
146            ("catcode", get_catcode()),
147            ("mathcode", get_mathcode()),
148        ])
149    }
150
151    test_suite![
152        expansion_equality_tests(
153            (catcode_base_case, r"\catcode 48 11 \the\catcode 48", r"11"),
154            (
155                catcode_grouping,
156                r"{\catcode 48 11 \the\catcode 48}-\the\catcode 48",
157                r"11-12"
158            ),
159            (catcode_default, r"\the\catcode 48", r"12"),
160            (
161                mathcode_base_case,
162                r"\mathcode 48 11 \the\mathcode 48",
163                r"11"
164            ),
165            (
166                mathcode_grouping,
167                r"{\mathcode 48 11 \the\mathcode 48}-\the\mathcode 48",
168                r"11-0"
169            ),
170            (mathcode_default, r"\the\mathcode 48", r"0"),
171        ),
172        serde_tests(
173            (catcode_serde_low, r"\catcode 48 11 ", r"\the\catcode 48"),
174            (catcode_serde_high, r"\catcode 480 11 ", r"\the\catcode 480"),
175            (
176                catcode_serde_group,
177                r"\catcode 65 7 {r\catcode 65 8 ",
178                r"\the\catcode 65 } \the\catcode 65"
179            ),
180            (mathcode_serde_low, r"\mathcode 48 11 ", r"\the\mathcode 48"),
181            (
182                mathcode_serde_high,
183                r"\mathcode 480 11 ",
184                r"\the\mathcode 480"
185            ),
186            (
187                mathcode_serde_group,
188                r"\mathcode 65 7 {r\mathcode 65 8 ",
189                r"\the\mathcode 65 } \the\mathcode 65"
190            ),
191        ),
192        recoverable_failure_tests(
193            (
194                catcode_value_too_large,
195                r"\catcode 48 16 \the\catcode 48",
196                "0"
197            ),
198            (
199                catcode_value_is_negative,
200                r"\catcode 48 -1 \the\catcode 48",
201                "0"
202            ),
203            (
204                mathcode_value_too_large,
205                r"\mathcode 48 33000 \the\mathcode 48",
206                "0"
207            ),
208            (
209                mathcode_value_is_negative,
210                r"\mathcode 48 -1 \the\mathcode 48",
211                "0"
212            ),
213        ),
214    ];
215}