texlang_stdlib/
registers.rs

1//! Register variables (`\count`, `\countdef`)
2
3use texlang::parse::OptionalEquals;
4use texlang::prelude as txl;
5use texlang::traits::*;
6use texlang::variable::SupportedType;
7use texlang::*;
8
9pub const COUNT_DOC: &str = "Get or set an integer register";
10pub const COUNTDEF_DOC: &str = "Bind an integer register to a control sequence";
11
12/// See [Component].
13pub struct DefaultMarker;
14
15/// Component required to have registers of type `T`.
16///
17/// The `Marker` generic parameter exists so that a single state type
18/// can contain multiple copies of this component (`Component<MarkerOne>`,
19/// `Component<MarkerTwo>`, etc.) and implement that HasComponent pattern
20/// multiple times. This allows for multiple register commands of the same
21/// type to be included in the same VM.
22pub struct Component<T, const N: usize, Marker = DefaultMarker>(
23    // We currently box the values because putting them directly on the stack causes the
24    // message pack decoder to stack overflow. It's a pity that we have to pay a runtime
25    // cost due to this, and it would be nice to fix the issue another way.
26    Box<[T; N]>,
27    std::marker::PhantomData<Marker>,
28);
29
30impl<T, const N: usize, Marker> Component<T, N, Marker> {
31    pub fn values(&self) -> &[T; N] {
32        &self.0
33    }
34}
35
36static COUNTDEF_TAG: command::StaticTag = command::StaticTag::new();
37
38pub fn countdef_tag() -> command::Tag {
39    COUNTDEF_TAG.get()
40}
41
42#[cfg(feature = "serde")]
43impl<T: serde::Serialize, const N: usize> serde::Serialize for Component<T, N> {
44    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
45    where
46        S: serde::Serializer,
47    {
48        let v: Vec<&T> = self.0.iter().collect();
49        v.serialize(serializer)
50    }
51}
52
53#[cfg(feature = "serde")]
54impl<'de, T: std::fmt::Debug + serde::Deserialize<'de>, const N: usize> serde::Deserialize<'de>
55    for Component<T, N>
56{
57    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
58    where
59        D: serde::Deserializer<'de>,
60    {
61        let v = Vec::<T>::deserialize(deserializer)?;
62        let a: Box<[T; N]> = v.try_into().unwrap();
63        Ok(Component(a, Default::default()))
64    }
65}
66
67impl<T: std::fmt::Debug + Default, const N: usize, Marker> Default for Component<T, N, Marker> {
68    fn default() -> Self {
69        let mut v = vec![];
70        for _ in 0..N {
71            v.push(T::default())
72        }
73        Self(Box::new(v.try_into().unwrap()), Default::default())
74    }
75}
76
77/// Get the `\count` command.
78pub fn get_count<S: HasComponent<Component<i32, N>>, const N: usize>() -> command::BuiltIn<S> {
79    new_registers_command()
80}
81
82/// Get the `\dimen` command.
83pub fn get_dimen<S: HasComponent<Component<common::Scaled, N>>, const N: usize>(
84) -> command::BuiltIn<S> {
85    new_registers_command()
86}
87
88/// Get the `\skip` command.
89pub fn get_skip<S: HasComponent<Component<common::Glue, N>>, const N: usize>() -> command::BuiltIn<S>
90{
91    new_registers_command()
92}
93
94/// Get the `\toks` command.
95pub fn get_toks<S: HasComponent<Component<Vec<token::Token>, N>>, const N: usize>(
96) -> command::BuiltIn<S> {
97    new_registers_command()
98}
99
100/// Creates a new registers command that stores values in the component.
101pub fn new_registers_command<
102    T: SupportedType,
103    Marker: 'static,
104    S: HasComponent<Component<T, N, Marker>>,
105    const N: usize,
106>() -> command::BuiltIn<S> {
107    variable::Command::new_array(ref_fn, mut_fn, variable::IndexResolver::Dynamic(count_fn)).into()
108}
109
110fn count_fn<T, Marker: 'static, S: HasComponent<Component<T, N, Marker>>, const N: usize>(
111    _: token::Token,
112    input: &mut vm::ExpandedStream<S>,
113) -> txl::Result<variable::Index> {
114    let index = parse::Uint::<N>::parse(input)?;
115    Ok(index.0.into())
116}
117
118/// Get the `\countdef` command.
119pub fn get_countdef<S: HasComponent<Component<i32, N>>, const N: usize>() -> command::BuiltIn<S> {
120    command::BuiltIn::new_execution(countdef_fn).with_tag(countdef_tag())
121}
122
123/// Get the `\toksdef` command.
124pub fn get_toksdef<S: HasComponent<Component<Vec<token::Token>, N>>, const N: usize>(
125) -> command::BuiltIn<S> {
126    command::BuiltIn::new_execution(countdef_fn).with_tag(countdef_tag())
127}
128
129fn countdef_fn<T: variable::SupportedType, S: HasComponent<Component<T, N>>, const N: usize>(
130    _: token::Token,
131    input: &mut vm::ExecutionInput<S>,
132) -> txl::Result<()> {
133    let scope = TexlangState::variable_assignment_scope_hook(input.state_mut());
134    let (cmd_ref_or, _, index) =
135        <(Option<token::CommandRef>, OptionalEquals, parse::Uint<N>)>::parse(input)?;
136    if let Some(cmd_ref) = cmd_ref_or {
137        input.commands_map_mut().insert_variable_command(
138            cmd_ref,
139            variable::Command::new_array(
140                ref_fn,
141                mut_fn,
142                variable::IndexResolver::Static(index.0.into()),
143            ),
144            scope,
145        );
146    }
147    Ok(())
148}
149
150fn ref_fn<T, Marker: 'static, S: HasComponent<Component<T, N, Marker>>, const N: usize>(
151    state: &S,
152    index: variable::Index,
153) -> &T {
154    state.component().0.get(index.0).unwrap()
155}
156
157fn mut_fn<T, Marker: 'static, S: HasComponent<Component<T, N, Marker>>, const N: usize>(
158    state: &mut S,
159    index: variable::Index,
160) -> &mut T {
161    state.component_mut().0.get_mut(index.0).unwrap()
162}
163
164#[cfg(test)]
165mod tests {
166    use std::collections::HashMap;
167
168    use super::*;
169    use crate::{prefix, the};
170    use texlang::vm::implement_has_component;
171    use texlang_testing::*;
172
173    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
174    #[derive(Default)]
175    struct State {
176        registers_i32: Component<i32, 256>,
177        registers_dimen: Component<common::Scaled, 256>,
178        registers_token_list: Component<Vec<token::Token>, 256>,
179        prefix: prefix::Component,
180        testing: TestingComponent,
181    }
182
183    impl TexlangState for State {
184        fn em_width(&self) -> common::Scaled {
185            self.registers_dimen.0[254]
186        }
187        fn ex_height(&self) -> common::Scaled {
188            self.registers_dimen.0[255]
189        }
190        fn recoverable_error_hook(
191            &self,
192            recoverable_error: error::TracedTexError,
193        ) -> Result<(), Box<dyn error::TexError>> {
194            TestingComponent::recoverable_error_hook(self, recoverable_error)
195        }
196        fn variable_assignment_scope_hook(
197            state: &mut Self,
198        ) -> texcraft_stdext::collections::groupingmap::Scope {
199            prefix::variable_assignment_scope_hook(state)
200        }
201    }
202    impl the::TheCompatible for State {}
203
204    implement_has_component![State{
205        registers_i32: Component<i32, 256>,
206        registers_dimen: Component<common::Scaled, 256>,
207        registers_token_list: Component<Vec<token::Token>, 256>,
208        prefix: prefix::Component,
209        testing: TestingComponent,
210    }];
211
212    fn built_in_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
213        HashMap::from([
214            ("the", the::get_the()),
215            ("count", get_count()),
216            ("countdef", get_countdef()),
217            ("dimen", get_dimen()),
218            ("global", prefix::get_global()),
219            ("toks", get_toks()),
220            ("toksdef", get_toksdef()),
221        ])
222    }
223
224    test_suite![
225        expansion_equality_tests(
226            (write_and_read_register, r"\count 23 4 \the\count 23", r"4"),
227            (
228                write_and_read_register_eq,
229                r"\count 23 = 4 \the\count 23",
230                r"4"
231            ),
232            (
233                negative_negative,
234                r"\count 1=5000 \count 0=-1 \the \count -\count 0",
235                r"5000"
236            ),
237            (countdef_base_case, r"\countdef\A 23\A 4 \the\A", r"4"),
238            (countdef_base_case_eq, r"\countdef\A = 23\A 4 \the\A", r"4"),
239            (
240                countdef_with_count,
241                r"\countdef\A 23\A 4\count 1 0 \the\A",
242                r"4"
243            ),
244            (
245                countdef_local,
246                r"\count 1=1 \count 2=2 \countdef\A 1{\countdef\A 2}\the\A",
247                r"1"
248            ),
249            (
250                countdef_global,
251                r"\count 1=1 \count 2=2 \countdef\A 1{\global\countdef\A 2}\the\A",
252                r"2"
253            ),
254            (
255                countdef_with_same_count,
256                r"\countdef\A 23\A 4\count 23 5 \the\A",
257                r"5"
258            ),
259            (
260                toks_basic,
261                r"\toks 1 = {Hola, Mundo}\the \toks 1",
262                r"Hola, Mundo"
263            ),
264            (
265                toksdef_basic,
266                r"\toksdef\content 1 \toks 1 = {Hola, Mundo}\the \content",
267                r"Hola, Mundo"
268            ),
269            (
270                toks_copy,
271                r"\toks 1 = {Hola, Mundo}\toks 2 = \toks 1 \the \toks 2",
272                r"Hola, Mundo"
273            ),
274            (
275                dimen_to_int_1,
276                r"\dimen 1 = 40sp \count 1 = \dimen 1 \the \count 1",
277                r"40",
278            ),
279            (
280                dimen_to_int_2,
281                r"\dimen 1 = 40in \count 1 = \dimen 1 \the \count 1",
282                r"189451468",
283            ),
284            (
285                int_to_dimen_1,
286                r"\count 1 = 40 \dimen 1 = \count 1 pt \the \dimen 1",
287                r"40.0pt",
288            ),
289            (
290                int_to_dimen_2,
291                r"\count 1 = -40 \dimen 1 = \count 1 pt \the \dimen 1",
292                r"-40.0pt",
293            ),
294            (
295                int_to_dimen_3,
296                // <int><int> is interpreted as <int>*<int>sp, so the result here is 40*2sp
297                r"\count 3 = 40 \count 5 = 2 \dimen 7 = \count 3 \count 5 \the \dimen 7",
298                r"0.00122pt",
299            ),
300            (
301                int_to_dimen_4,
302                r"\count 1 = 40 \dimen 2 = 5sp \dimen 1 = \count 1 \dimen 2 \the \dimen 1",
303                r"0.00305pt",
304            ),
305            (
306                int_to_dimen_5,
307                r"\count 1 = 0 \dimen 2 = 5sp \dimen 1 = \count 1 \dimen 2 \the \dimen 1",
308                r"0.0pt",
309            ),
310            (
311                dimen_to_dimen_1,
312                r"\dimen 1 = 10pt \dimen 2 = 5 \dimen 1 \the \dimen 2",
313                r"50.0pt",
314            ),
315            (
316                dimen_to_dimen_2,
317                r"\dimen 2 = 10pt \dimen 1 = - 1.5 \dimen 2 \the \dimen 1",
318                r"-15.0pt",
319            ),
320            (
321                dimen_to_dimen_3,
322                r"\dimen 2 = 5pt \dimen 1 = - 1.5 \dimen 2 \the \dimen 1",
323                r"-7.5pt",
324            ),
325            (
326                dimen_to_dimen_4,
327                r"\dimen 2 = -10pt \dimen 1 = 1.5 \dimen 2 \the \dimen 1",
328                r"-15.0pt",
329            ),
330            (
331                dimen_to_dimen_5,
332                r"\dimen 2 = -5pt \dimen 1 = 1.5 \dimen 2 \the \dimen 1",
333                r"-7.5pt",
334            ),
335            (
336                dimen_to_dimen_6,
337                r"\dimen 2 = -10pt \dimen 1 = - 1.5 \dimen 2 \the \dimen 1",
338                r"15.0pt",
339            ),
340            (
341                dimen_to_dimen_7,
342                r"\dimen 2 = -5pt \dimen 1 = - 1.5 \dimen 2 \the \dimen 1",
343                r"7.5pt",
344            ),
345            (
346                int_dimen_to_dimen_1,
347                r"\count 1 = 2 \dimen 1 = 3pt \dimen 2 = \count 1 \dimen 1 \the \dimen 2",
348                r"6.0pt",
349            ),
350            (
351                int_dimen_to_dimen_2,
352                r"\count 1 = -2 \dimen 1 = 3pt \dimen 2 = \count 1 \dimen 1 \the \dimen 2",
353                r"-6.0pt",
354            ),
355            (
356                int_dimen_to_dimen_3,
357                r"\count 1 = 2 \dimen 1 = -3pt \dimen 2 = \count 1 \dimen 1 \the \dimen 2",
358                r"-6.0pt",
359            ),
360            (
361                int_dimen_to_dimen_4,
362                r"\count 1 = 2 \dimen 1 = 3pt \dimen 2 = -\count 1 \dimen 1 \the \dimen 2",
363                r"-6.0pt",
364            ),
365            (
366                int_dimen_to_dimen_5,
367                r"\count 1 = 2 \dimen 1 = 0pt \dimen 2 = -\count 1 \dimen 1 \the \dimen 2",
368                r"0.0pt",
369            ),
370            (
371                em_dimen,
372                r"\dimen 254 = 2.25pt \dimen 0 = 4.5em \the \dimen 0",
373                r"10.125pt",
374            ),
375            (
376                ex_dimen,
377                r"\dimen 255 = 2.25pt \dimen 0 = 4.5ex \the \dimen 0",
378                r"10.125pt",
379            ),
380        ),
381        serde_tests(
382            (serde_basic, r"\count 100 200 ", r"\the \count 100"),
383            (serde_countdef, r"\countdef \A 100 \A = 200 ", r"\the \A"),
384            (
385                serde_group,
386                r"\count 1 2 {\count 1 3 ",
387                r"\the \count 1 } \the \count 1"
388            ),
389        ),
390        recoverable_failure_tests(
391            (
392                write_register_index_too_big,
393                r"\count 260 = 4 \the\count 0",
394                "4"
395            ),
396            (
397                write_register_negative_index,
398                r"\count -1 = 4 \the\count 0",
399                "4"
400            ),
401            (
402                countdef_register_index_too_big,
403                r"\countdef\A 260 \A= 4 \the\count 0",
404                "4"
405            ),
406            (countdef_missing_cs, r"\countdef 260 End", "End"),
407            (
408                dimen_to_dimen_too_big_1,
409                r"\dimen 1 = 10000pt \dimen 2 = 2 \dimen 1 \the \dimen 2",
410                r"16383.99998pt",
411            ),
412            (
413                dimen_to_dimen_too_big_2,
414                r"\dimen 1 = -10000pt \dimen 2 = 2 \dimen 1 \the \dimen 2",
415                r"-16383.99998pt",
416            ),
417            (
418                dimen_to_dimen_too_big_3,
419                r"\dimen 1 = 10000pt \dimen 2 = -2 \dimen 1 \the \dimen 2",
420                r"-16383.99998pt",
421            ),
422            (
423                dimen_to_dimen_too_big_4,
424                r"\dimen 1 = -10000pt \dimen 2 = -2 \dimen 1 \the \dimen 2",
425                r"16383.99998pt",
426            ),
427            (
428                int_to_dimen_too_big_1,
429                r"\count 1 = 400 \dimen 1 = \count 1 in \the \dimen 1",
430                r"16383.99998pt",
431            ),
432            (
433                int_to_dimen_too_big_2,
434                r"\count 1 = -400 \dimen 1 = \count 1 in \the \dimen 1",
435                r"-16383.99998pt",
436            ),
437            (
438                int_to_dimen_too_big_3,
439                r"\count 1 = 400 \dimen 1 = - \count 1 in \the \dimen 1",
440                r"-16383.99998pt",
441            ),
442            (
443                int_to_dimen_too_big_4,
444                r"\count 1 = -400 \dimen 1 = - \count 1 in \the \dimen 1",
445                r"16383.99998pt",
446            ),
447            (
448                int_dimen_to_dimen_too_big_1,
449                r"\count 1 = 2 \dimen 1 = 10000pt \dimen 2 = \count 1 \dimen 1 \the \dimen 2",
450                r"16383.99998pt",
451            ),
452            (
453                int_dimen_to_dimen_too_big_2,
454                r"\count 1 = 2 \dimen 1 = 10000pt \dimen 2 = -\count 1 \dimen 1 \the \dimen 2",
455                r"-16383.99998pt",
456            ),
457            (
458                int_dimen_to_dimen_too_big_3,
459                r"\count 1 = -2 \dimen 1 = 10000pt \dimen 2 = \count 1 \dimen 1 \the \dimen 2",
460                r"-16383.99998pt",
461            ),
462            (
463                int_dimen_to_dimen_too_big_4,
464                r"\count 1 = 2 \dimen 1 = -10000pt \dimen 2 = \count 1 \dimen 1 \the \dimen 2",
465                r"-16383.99998pt",
466            ),
467        ),
468    ];
469}