texlang_stdlib/
catcode.rs1use 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
58pub 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}