texlang_stdlib/
math.rs

1//! Operations on variables (add, multiply, divide)
2
3use crate::prefix;
4use texlang::parse::OptionalBy;
5use texlang::traits::*;
6use texlang::*;
7
8/// Get the `\advance` command.
9pub fn get_advance<S: HasComponent<prefix::Component>>() -> command::BuiltIn<S> {
10    get_command::<S, AddOp>()
11}
12
13/// Get the `\advanceChecked` command.
14pub fn get_advance_checked<S: HasComponent<prefix::Component>>() -> command::BuiltIn<S> {
15    get_command::<S, AddCheckedOp>()
16}
17
18/// Get the `\multiply` command.
19pub fn get_multiply<S: HasComponent<prefix::Component>>() -> command::BuiltIn<S> {
20    get_command::<S, MultiplyOp>()
21}
22
23/// Get the `\multiplyWrapped` command.
24pub fn get_multiply_wrapped<S: HasComponent<prefix::Component>>() -> command::BuiltIn<S> {
25    get_command::<S, MultiplyWrappedOp>()
26}
27
28/// Get the `\divide` command.
29pub fn get_divide<S: HasComponent<prefix::Component>>() -> command::BuiltIn<S> {
30    get_command::<S, DivideOp>()
31}
32
33fn get_command<S: HasComponent<prefix::Component>, O: Op>() -> command::BuiltIn<S> {
34    command::BuiltIn::new_execution(math_primitive_fn::<S, O>)
35        .with_tag(get_variable_op_tag())
36        .with_doc(O::DOC)
37}
38
39static VARIABLE_OP_TAG: command::StaticTag = command::StaticTag::new();
40
41pub fn get_variable_op_tag() -> command::Tag {
42    VARIABLE_OP_TAG.get()
43}
44
45trait Op {
46    const DOC: &'static str = "";
47    fn perform(lhs: i32, rhs: i32) -> Result<i32, Box<error::Error>>;
48}
49
50struct AddOp;
51
52impl Op for AddOp {
53    const DOC: &'static str = "Add an integer to a variable";
54    fn perform(lhs: i32, rhs: i32) -> Result<i32, Box<error::Error>> {
55        // Note: TeX explicitly permits overflow in \advance
56        Ok(lhs.wrapping_add(rhs))
57    }
58}
59
60struct AddCheckedOp;
61
62impl Op for AddCheckedOp {
63    const DOC: &'static str = "Add an integer to a variable and error on overflow";
64    fn perform(lhs: i32, rhs: i32) -> Result<i32, Box<error::Error>> {
65        match lhs.checked_add(rhs) {
66            Some(result) => Ok(result),
67            None => Err(OverflowError {
68                op_name: "addition",
69                lhs,
70                rhs,
71                wrapped_result: lhs.wrapping_add(rhs),
72            }
73            .into()),
74        }
75    }
76}
77
78struct MultiplyOp;
79
80impl Op for MultiplyOp {
81    const DOC: &'static str = "Multiply a variable by an integer";
82    fn perform(lhs: i32, rhs: i32) -> Result<i32, Box<error::Error>> {
83        match lhs.checked_mul(rhs) {
84            Some(result) => Ok(result),
85            None => Err(OverflowError {
86                op_name: "multiplication",
87                lhs,
88                rhs,
89                wrapped_result: lhs.wrapping_mul(rhs),
90            }
91            .into()),
92        }
93    }
94}
95
96struct MultiplyWrappedOp;
97
98impl Op for MultiplyWrappedOp {
99    const DOC: &'static str = "Multiply a variable by an integer and wrap on overflow";
100    fn perform(lhs: i32, rhs: i32) -> Result<i32, Box<error::Error>> {
101        Ok(lhs.wrapping_mul(rhs))
102    }
103}
104
105struct DivideOp;
106
107impl Op for DivideOp {
108    const DOC: &'static str = "Divide a variable by an integer";
109    fn perform(lhs: i32, rhs: i32) -> Result<i32, Box<error::Error>> {
110        if rhs == 0 {
111            return Err(DivisionByZeroError { numerator: lhs }.into());
112        }
113        Ok(lhs.wrapping_div(rhs))
114    }
115}
116
117#[derive(Debug)]
118struct OverflowError {
119    op_name: &'static str,
120    lhs: i32,
121    rhs: i32,
122    wrapped_result: i32,
123}
124
125impl error::TexError for OverflowError {
126    fn kind(&self) -> error::Kind {
127        error::Kind::FailedPrecondition
128    }
129
130    fn title(&self) -> String {
131        format!["overflow in checked {}", self.op_name]
132    }
133
134    fn notes(&self) -> Vec<error::display::Note> {
135        vec![
136            format!["left hand side evaluated to {}", self.lhs].into(),
137            format!["right hand side evaluated to {}", self.rhs].into(),
138            format!["wrapped result would be {}", self.wrapped_result].into(),
139        ]
140    }
141}
142
143#[derive(Debug)]
144struct DivisionByZeroError {
145    numerator: i32,
146}
147
148impl error::TexError for DivisionByZeroError {
149    fn kind(&self) -> error::Kind {
150        error::Kind::FailedPrecondition
151    }
152
153    fn title(&self) -> String {
154        "division by zero".into()
155    }
156
157    fn notes(&self) -> Vec<error::display::Note> {
158        vec![format!["numerator evaluated to {}", self.numerator].into()]
159    }
160}
161
162fn math_primitive_fn<S: HasComponent<prefix::Component>, O: Op>(
163    token: token::Token,
164    input: &mut vm::ExecutionInput<S>,
165) -> Result<(), Box<error::Error>> {
166    let scope = input.state_mut().component_mut().read_and_reset_global();
167    let variable = variable::Variable::parse(input)?;
168    OptionalBy::parse(input)?;
169    match variable {
170        variable::Variable::Int(variable) => {
171            let lhs = *variable.get(input.state());
172            let rhs = i32::parse(input)?;
173            let result = O::perform(lhs, rhs)?;
174            variable.set(input, scope, result);
175            Ok(())
176        }
177        variable::Variable::CatCode(_) => invalid_variable_error(input.vm(), token),
178    }
179}
180
181fn invalid_variable_error<S>(vm: &vm::VM<S>, token: token::Token) -> Result<(), Box<error::Error>> {
182    Err(error::SimpleTokenError::new(
183        vm,
184        token,
185        "arithmetic commands cannot be applied to variables of type X",
186    )
187    .into())
188    // TODO .add_note(
189    //       "arithmetic commands (\\advance, \\multiply, \\divide) can be applied to integer, dimension, glue and muglue variables",
190}
191
192#[cfg(test)]
193mod tests {
194    use std::collections::HashMap;
195
196    use super::*;
197    use crate::catcode;
198    use crate::registers;
199    use crate::script;
200    use crate::testing::*;
201    use crate::the;
202    use texlang::vm::implement_has_component;
203
204    #[derive(Default)]
205    struct State {
206        catcode: catcode::Component,
207        prefix: prefix::Component,
208        registers: registers::Component<i32, 256>,
209        script: script::Component,
210    }
211
212    impl TexlangState for State {}
213
214    implement_has_component![
215        State,
216        (catcode::Component, catcode),
217        (prefix::Component, prefix),
218        (registers::Component<i32, 256>, registers),
219        (script::Component, script),
220    ];
221
222    fn initial_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
223        HashMap::from([
224            ("advance", get_advance()),
225            ("advanceChecked", get_advance_checked()),
226            ("multiply", get_multiply()),
227            ("multiplyWrapped", get_multiply_wrapped()),
228            ("divide", get_divide()),
229            //
230            ("catcode", catcode::get_catcode()),
231            ("count", registers::get_count()),
232            ("global", prefix::get_global()),
233            ("the", the::get_the()),
234        ])
235    }
236
237    macro_rules! arithmetic_tests {
238        ( $( ($name: ident, $op: expr, $lhs: expr, $rhs: expr, $expected: expr) ),* $(,)? ) => {
239            test_suite![
240                expansion_equality_tests(
241                    $(
242                        (
243                            $name,
244                            format![r"\count 1 {} {} \count 1 {} \the\count 1", $lhs, $op, $rhs],
245                            $expected
246                        ),
247                    )*
248                )
249            ];
250        };
251    }
252
253    arithmetic_tests![
254        (advance_base_case, r"\advance", "1", "2", "3"),
255        (advance_base_case_with_by, r"\advance", "1", "by 2", "3"),
256        (advance_negative_summand, r"\advance", "10", "-2", "8"),
257        (
258            advance_overflow_case,
259            r"\advance",
260            "2147483647",
261            "1",
262            "-2147483648"
263        ),
264        (multiply_base_case, r"\multiply", "5", "4", "20"),
265        (multiply_base_case_with_by, r"\multiply", "5", "by 4", "20"),
266        (multiply_pos_neg, r"\multiply", "-5", "4", "-20"),
267        (multiply_neg_pos, r"\multiply", "5", "-4", "-20"),
268        (multiply_neg_neg, r"\multiply", "-5", "-4", "20"),
269        (
270            multiply_wrapping_overflow,
271            r"\multiplyWrapped",
272            "100000",
273            "100000",
274            "1410065408"
275        ),
276        (
277            multiply_wrapping_base_case,
278            r"\multiplyWrapped",
279            "5",
280            "4",
281            "20"
282        ),
283        (divide_base_case, r"\divide", "9", "4", "2"),
284        (divide_with_by, r"\divide", "9", "by 4", "2"),
285        (divide_pos_neg, r"\divide", "-9", "4", "-2"),
286        (divide_neg_pos, r"\divide", "9", "-4", "-2"),
287        (divide_neg_neg, r"\divide", "-9", "-4", "2"),
288        (divide_exact, r"\divide", "100", "10", "10"),
289        (advance_checked_base_case, r"\advanceChecked", "1", "2", "3"),
290        (
291            advance_checked_negative_summand,
292            r"\advanceChecked",
293            "10",
294            "-2",
295            "8"
296        )
297    ];
298
299    test_suite![
300        expansion_equality_tests(
301            (
302                advance_x_by_x,
303                r"\count 1 200 \advance \count 1 by \count 1 a\the\count 1",
304                r"a400"
305            ),
306            (
307                global_advance,
308                r"\count 1 5{\global\advance\count 1 8}\the\count 1",
309                "13"
310            ),
311            (
312                local_advance,
313                r"\count 1 5{\advance\count 1 8}\the\count 1",
314                "5"
315            ),
316        ),
317        failure_tests(
318            (
319                advance_incorrect_keyword_1,
320                r"\count 1 1\advance\count 1 fy 2 \the \count 1"
321            ),
322            (
323                advance_incorrect_keyword_2,
324                r"\count 1 1\advance\count 1 be 2 \the \count 1"
325            ),
326            (advance_catcode_not_supported, r"\advance\catcode 100 by 2"),
327            (
328                advance_checked_overflow,
329                r"\count 1 2147483647 \advanceChecked\count 1 by 1"
330            ),
331            (
332                multiply_overflow,
333                r"\count 1 100000 \multiply\count 1 by 100000"
334            ),
335            (divide_by_zero, r"\divide\count 1 by 0"),
336        ),
337    ];
338}