texlang_stdlib/
alias.rs

1//! `\let` aliasing command
2
3use crate::prefix;
4use texlang::parse::OptionalEqualsUnexpanded;
5use texlang::prelude as txl;
6use texlang::traits::*;
7use texlang::*;
8
9pub const LET_DOC: &str = "Assign a command or character to a control sequence";
10
11/// Get the `\let` command.
12pub fn get_let<S: HasComponent<prefix::Component>>() -> command::BuiltIn<S> {
13    command::BuiltIn::new_execution(let_primitive_fn)
14        .with_tag(let_tag())
15        .with_doc(LET_DOC)
16}
17
18static LET_TAG: command::StaticTag = command::StaticTag::new();
19
20pub fn let_tag() -> command::Tag {
21    LET_TAG.get()
22}
23
24fn let_primitive_fn<S: HasComponent<prefix::Component>>(
25    _: token::Token,
26    input: &mut vm::ExecutionInput<S>,
27) -> txl::Result<()> {
28    // TeX.2021.1221
29    let scope = TexlangState::variable_assignment_scope_hook(input.state_mut());
30    let cmd_ref_or = Option::<token::CommandRef>::parse(input)?;
31    OptionalEqualsUnexpanded::parse(input)?;
32    let token = input.unexpanded().next_or_err(LetEndOfInputError {})?;
33    if let Some(cmd_ref) = cmd_ref_or {
34        match token.value() {
35            token::Value::CommandRef(command_ref) => {
36                input
37                    .commands_map_mut()
38                    .alias_control_sequence(cmd_ref, &command_ref, scope);
39            }
40            _ => {
41                input.commands_map_mut().alias_token(cmd_ref, token, scope);
42            }
43        };
44    };
45    Ok(())
46}
47
48#[derive(Debug)]
49struct LetEndOfInputError;
50
51impl error::EndOfInputError for LetEndOfInputError {
52    fn doing(&self) -> String {
53        r"parsing the right hand side of a \let assignment".into()
54    }
55}
56
57#[cfg(test)]
58mod test {
59    use std::collections::HashMap;
60
61    use super::*;
62    use crate::def;
63    use crate::the;
64    use texlang_testing::*;
65
66    #[derive(Default)]
67    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68    struct State {
69        prefix: prefix::Component,
70        testing: TestingComponent,
71    }
72
73    implement_has_component![State {
74        prefix: prefix::Component,
75        testing: TestingComponent,
76    }];
77    impl the::TheCompatible for State {}
78
79    impl TexlangState for State {
80        fn variable_assignment_scope_hook(
81            state: &mut Self,
82        ) -> texcraft_stdext::collections::groupingmap::Scope {
83            prefix::variable_assignment_scope_hook(state)
84        }
85        fn recoverable_error_hook(
86            &self,
87            recoverable_error: error::TracedTexError,
88        ) -> Result<(), Box<dyn error::TexError>> {
89            TestingComponent::recoverable_error_hook(self, recoverable_error)
90        }
91    }
92
93    fn built_in_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
94        HashMap::from([
95            ("def", def::get_def()),
96            ("global", prefix::get_global()),
97            ("integer", TestingComponent::get_integer()),
98            ("let", get_let()),
99            ("the", the::get_the()),
100        ])
101    }
102
103    test_suite![
104        expansion_equality_tests(
105            (let_for_macro, r"\def\A{abc}\let\B\A\B", "abc"),
106            (let_for_macro_active_char, r"\def\A{abc}\let~\A~", "abc"),
107            (local, r"\def\A{a}\def\B{b}\let\C=\A{\let\C=\B \C}\C", "ba"),
108            (
109                global,
110                r"\def\A{a}\def\B{b}\let\C=\A{\global\let\C=\B \C}\C",
111                "bb"
112            ),
113            (let_for_macro_equals, r"\def\A{abc}\let\B=\A\B", "abc"),
114            (let_character, r"\let\A=B\A", "B"),
115            (let_unknown_cs_name, r"\let \B=\undefined", ""),
116        ),
117        recoverable_failure_tests((missing_cs, r"\def\B{Hello}\let A=\B World", "=HelloWorld"),),
118        serde_tests(
119            (
120                alias_execution_primitive,
121                r"\let\defNew=\def",
122                r"\defNew\A{abc}\A"
123            ),
124            (
125                alias_expansion_primitive,
126                r"\let\theNew=\the",
127                r"\integer=3\theNew\integer"
128            ),
129            (
130                alias_variable_singleton,
131                r"\let\integerNew=\integer",
132                r"\integerNew=3\the\integer"
133            ),
134            (serde_macro, r"\def\A{Hello World}\let\B=\A ", r"\A \B",),
135            (serde_character, r"\let\A=B ", r"\A",),
136        ),
137    ];
138}