1use crate::prefix;
4use texlang::parse::OptionalBy;
5use texlang::traits::*;
6use texlang::*;
7
8pub fn get_advance<S: HasComponent<prefix::Component>>() -> command::BuiltIn<S> {
10 get_command::<S, AddOp>()
11}
12
13pub fn get_advance_checked<S: HasComponent<prefix::Component>>() -> command::BuiltIn<S> {
15 get_command::<S, AddCheckedOp>()
16}
17
18pub fn get_multiply<S: HasComponent<prefix::Component>>() -> command::BuiltIn<S> {
20 get_command::<S, MultiplyOp>()
21}
22
23pub fn get_multiply_wrapped<S: HasComponent<prefix::Component>>() -> command::BuiltIn<S> {
25 get_command::<S, MultiplyWrappedOp>()
26}
27
28pub 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 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 }
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 ("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}