1use 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 }
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#[inline]
66pub fn cat_code<S: HasComponent<Component<CatCode>>>(state: &S, c: char) -> CatCode {
67 get_value(state, c)
68}
69
70pub fn get_catcode<S: HasComponent<Component<CatCode>>>() -> command::BuiltIn<S> {
72 get_command::<CatCode, S>()
73}
74
75#[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
84pub fn get_mathcode<S: HasComponent<Component<types::MathCode>>>() -> command::BuiltIn<S> {
86 get_command::<types::MathCode, S>()
87}
88
89#[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}