1use 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
11pub 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 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}